Add stylus functionality to pointer manager

This commit is contained in:
Brad Davis 2017-10-25 15:11:18 -07:00
parent 5a7298f15b
commit f061b9c2da
32 changed files with 1019 additions and 530 deletions

View file

@ -1819,14 +1819,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
DependencyManager::get<EntityTreeRenderer>()->setMouseRayPickResultOperator([&](QUuid rayPickID) {
RayToEntityIntersectionResult entityResult;
entityResult.intersects = false;
QVariantMap result = DependencyManager::get<PickManager>()->getPrevPickResult(rayPickID);
if (result["type"].isValid()) {
entityResult.intersects = result["type"] != PickScriptingInterface::INTERSECTED_NONE();
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<RayPickResult>(rayPickID);
if (pickResult) {
entityResult.intersects = pickResult->type != IntersectionType::NONE;
if (entityResult.intersects) {
entityResult.intersection = vec3FromVariant(result["intersection"]);
entityResult.distance = result["distance"].toFloat();
entityResult.surfaceNormal = vec3FromVariant(result["surfaceNormal"]);
entityResult.entityID = result["objectID"].toUuid();
entityResult.intersection = pickResult->intersection;
entityResult.distance = pickResult->distance;
entityResult.surfaceNormal = pickResult->surfaceNormal;
entityResult.entityID = pickResult->objectID;
entityResult.entity = DependencyManager::get<EntityTreeRenderer>()->getTree()->findEntityByID(entityResult.entityID);
}
}
@ -4957,7 +4957,7 @@ void Application::update(float deltaTime) {
{
PROFILE_RANGE(app, "PointerManager");
DependencyManager::get<PointerManager>()->update();
DependencyManager::get<PointerManager>()->update(deltaTime);
}
{

View file

@ -20,7 +20,7 @@ JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOff
{
}
const PickRay JointRayPick::getMathematicalPick() const {
PickRay JointRayPick::getMathematicalPick() const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar";

View file

@ -18,7 +18,7 @@ class JointRayPick : public RayPick {
public:
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override;
PickRay getMathematicalPick() const override;
private:
std::string _jointName;

View file

@ -16,6 +16,7 @@
#include <DependencyManager.h>
#include <pointers/PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled) :
@ -177,17 +178,20 @@ void LaserPointer::disableRenderState(const RenderState& renderState) {
}
}
void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) {
IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt());
PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap());
QUuid uid = prevRayPickResult["objectID"].toUuid();
void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) {
float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat();
PickRay pickRay{ rayPickResult->pickVariant };
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
@ -195,8 +199,12 @@ void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) {
}
}
Pointer::PickedObject LaserPointer::getHoveredObject(const QVariantMap& pickResult) {
return Pointer::PickedObject(pickResult["objectID"].toUuid(), IntersectionType(pickResult["type"].toUInt()));
Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (!rayPickResult) {
return PickedObject();
}
return PickedObject(rayPickResult->objectID, rayPickResult->type);
}
Pointer::Buttons LaserPointer::getPressedButtons() {
@ -281,16 +289,22 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
return RenderState(startID, pathID, endID);
}
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const {
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const {
uint32_t id = 0;
glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]);
glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]);
QVariantMap searchRay = pickResult["searchRay"].toMap();
glm::vec3 direction = vec3FromVariant(searchRay["direction"]);
QUuid pickedID = pickResult["objectID"].toUuid();
QUuid pickedID;
glm::vec3 intersection, surfaceNormal, direction, origin;
if (target.type != NONE) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
intersection = rayPickResult->intersection;
surfaceNormal = rayPickResult->surfaceNormal;
const QVariantMap& searchRay = rayPickResult->pickVariant;
direction = vec3FromVariant(searchRay["direction"]);
origin = vec3FromVariant(searchRay["origin"]);
pickedID = rayPickResult->objectID;;
}
glm::vec2 pos2D;
if (pickedID != target.objectID) {
glm::vec3 origin = vec3FromVariant(searchRay["origin"]);
if (target.type == ENTITY) {
intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction);
} else if (target.type == OVERLAY) {
@ -355,4 +369,4 @@ glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const
glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint());
}
}

View file

@ -65,15 +65,15 @@ public:
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay) override;
void updateVisuals(const QVariantMap& prevRayPickResult) override;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
PickedObject getHoveredObject(const QVariantMap& pickResult) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons() override;
static RenderState buildRenderState(const QVariantMap& propMap);
protected:
PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override;
private:
PointerTriggers _triggers;

View file

@ -27,4 +27,8 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert
void LaserPointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const {
DependencyManager::get<PointerScriptingInterface>()->editRenderState(uid, renderState, properties);
}
}
QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(const QUuid& uid) const {
return DependencyManager::get<PointerScriptingInterface>()->getPrevPickResult(uid);
}

View file

@ -27,7 +27,7 @@ public slots:
Q_INVOKABLE void removeLaserPointer(const QUuid& uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE QVariantMap getPrevRayPickResult(QUuid uid) const { return DependencyManager::get<PointerManager>()->getPrevPickResult(uid); }
Q_INVOKABLE QVariantMap getPrevRayPickResult(const QUuid& uid) const;
Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }

View file

@ -18,7 +18,7 @@ MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, co
{
}
const PickRay MouseRayPick::getMathematicalPick() const {
PickRay MouseRayPick::getMathematicalPick() const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) {
QVariantMap posMap = position.toMap();

View file

@ -18,7 +18,7 @@ class MouseRayPick : public RayPick {
public:
MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override;
PickRay getMathematicalPick() const override;
};
#endif // hifi_MouseRayPick_h

View file

@ -94,7 +94,12 @@ void PickScriptingInterface::removePick(const QUuid& uid) {
}
QVariantMap PickScriptingInterface::getPrevPickResult(const QUuid& uid) {
return DependencyManager::get<PickManager>()->getPrevPickResult(uid);
QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
}
void PickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) {

View file

@ -10,9 +10,11 @@
#include <QtCore/QVariant>
#include <GLMHelpers.h>
#include <shared/QtHelpers.h>
#include "Application.h"
#include "LaserPointer.h"
#include "StylusPointer.h"
void PointerScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
@ -21,15 +23,40 @@ void PointerScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptV
DependencyManager::get<PointerManager>()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems));
}
QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) const {
QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) {
// Interaction with managers should always happen ont he main thread
if (QThread::currentThread() != qApp->thread()) {
QUuid result;
BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(QUuid, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties));
return result;
}
switch (type) {
case PickQuery::PickType::Ray:
return createLaserPointer(properties);
case PickQuery::PickType::Stylus:
return createStylus(properties);
default:
return QUuid();
}
}
QUuid PointerScriptingInterface::createStylus(const QVariant& properties) const {
bilateral::Side side = bilateral::Side::Invalid;
{
QVariant handVar = properties.toMap()["hand"];
if (handVar.isValid()) {
side = bilateral::side(handVar.toInt());
}
}
if (bilateral::Side::Invalid == side) {
return QUuid();
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(side));
}
QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@ -133,4 +160,14 @@ void PointerScriptingInterface::editRenderState(const QUuid& uid, const QString&
}
DependencyManager::get<PointerManager>()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps);
}
}
QVariantMap PointerScriptingInterface::getPrevPickResult(const QUuid& uid) const {
QVariantMap result;
auto pickResult = DependencyManager::get<PointerManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
}

View file

@ -20,15 +20,16 @@ class PointerScriptingInterface : public QObject, public Dependency {
public:
QUuid createLaserPointer(const QVariant& properties) const;
QUuid createStylus(const QVariant& properties) const;
public slots:
Q_INVOKABLE QUuid createPointer(const PickQuery::PickType& type, const QVariant& properties) const;
Q_INVOKABLE QUuid createPointer(const PickQuery::PickType& type, const QVariant& properties);
Q_INVOKABLE void enablePointer(const QUuid& uid) const { DependencyManager::get<PointerManager>()->enablePointer(uid); }
Q_INVOKABLE void disablePointer(const QUuid& uid) const { DependencyManager::get<PointerManager>()->disablePointer(uid); }
Q_INVOKABLE void removePointer(const QUuid& uid) const { DependencyManager::get<PointerManager>()->removePointer(uid); }
Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const;
Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { DependencyManager::get<PointerManager>()->setRenderState(uid, renderState.toStdString()); }
Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const { return DependencyManager::get<PointerManager>()->getPrevPickResult(uid); }
Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const;
Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { DependencyManager::get<PointerManager>()->setPrecisionPicking(uid, precisionPicking); }
Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { DependencyManager::get<PointerManager>()->setLength(uid, laserLength); }

View file

@ -53,7 +53,7 @@ public:
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer newRes) override {
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newRayRes = std::static_pointer_cast<RayPickResult>(newRes);
if (newRayRes->distance < distance) {
return std::make_shared<RayPickResult>(*newRayRes);

View file

@ -37,7 +37,12 @@ void RayPickScriptingInterface::removeRayPick(const QUuid& uid) {
}
QVariantMap RayPickScriptingInterface::getPrevRayPickResult(const QUuid& uid) {
return DependencyManager::get<PickManager>()->getPrevPickResult(uid);
QVariantMap result;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResult(uid);
if (pickResult) {
result = pickResult->toVariantMap();
}
return result;
}
void RayPickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) {

View file

@ -13,6 +13,6 @@ StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& directi
{
}
const PickRay StaticRayPick::getMathematicalPick() const {
PickRay StaticRayPick::getMathematicalPick() const {
return _pickRay;
}

View file

@ -15,7 +15,7 @@ class StaticRayPick : public RayPick {
public:
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
const PickRay getMathematicalPick() const override;
PickRay getMathematicalPick() const override;
private:
PickRay _pickRay;

View file

@ -0,0 +1,627 @@
//
// 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 <array>
#include <QtCore/QThread>
#include <DependencyManager.h>
#include <pointers/PickManager.h>
#include <GLMHelpers.h>
#include <Transform.h>
#include <shared/QtHelpers.h>
#include <controllers/StandardControls.h>
#include <controllers/UserInputMapper.h>
#include <RegisteredMetaTypes.h>
#include <MessagesClient.h>
#include <EntityItemID.h>
#include "Application.h"
#include "avatar/AvatarManager.h"
#include "avatar/MyAvatar.h"
#include "scripting/HMDScriptingInterface.h"
#include "ui/overlays/Web3DOverlay.h"
#include "ui/overlays/Sphere3DOverlay.h"
#include "avatar/AvatarManager.h"
#include "InterfaceLogging.h"
#include "PickScriptingInterface.h"
using namespace controller;
using namespace bilateral;
static Setting::Handle<double> USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false);
static const float WEB_STYLUS_LENGTH = 0.2f;
static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand
static const vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f };
static const float TABLET_MIN_HOVER_DISTANCE = 0.01f;
static const float TABLET_MAX_HOVER_DISTANCE = 0.1f;
static const float TABLET_MIN_TOUCH_DISTANCE = -0.05f;
static const float TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE;
static const float EDGE_BORDER = 0.075f;
static const float HOVER_HYSTERESIS = 0.01f;
static const float NEAR_HYSTERESIS = 0.05f;
static const float TOUCH_HYSTERESIS = 0.002f;
// triggered when stylus presses a web overlay/entity
static const float HAPTIC_STYLUS_STRENGTH = 1.0f;
static const float HAPTIC_STYLUS_DURATION = 20.0f;
static const float POINTER_PRESS_TO_MOVE_DELAY = 0.33f; // seconds
static const float WEB_DISPLAY_STYLUS_DISTANCE = 0.5f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f;
static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT;
std::array<StylusPointer*, 2> STYLUSES;
static OverlayID getHomeButtonID() {
return DependencyManager::get<HMDScriptingInterface>()->getCurrentHomeButtonID();
}
static OverlayID getTabletScreenID() {
return DependencyManager::get<HMDScriptingInterface>()->getCurrentTabletScreenID();
}
struct SideData {
QString avatarJoint;
QString cameraJoint;
controller::StandardPoseChannel channel;
controller::Hand hand;
vec3 grabPointSphereOffset;
int getJointIndex(bool finger) {
const auto& jointName = finger ? avatarJoint : cameraJoint;
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getJointIndex(jointName);
}
};
static const std::array<SideData, 2> SIDES{ { { "LeftHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
StandardPoseChannel::LEFT_HAND,
Hand::LEFT,
{ -0.04f, 0.13f, 0.039f } },
{ "RightHandIndex4",
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
StandardPoseChannel::RIGHT_HAND,
Hand::RIGHT,
{ 0.04f, 0.13f, 0.039f } } } };
static StylusTip getFingerWorldLocation(Side side) {
const auto& sideData = SIDES[index(side)];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint);
if (-1 == fingerJointIndex) {
return StylusTip();
}
auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
auto avatarOrientation = myAvatar->getOrientation();
auto avatarPosition = myAvatar->getPosition();
StylusTip result;
result.side = side;
result.orientation = avatarOrientation * fingerRotation;
result.position = avatarPosition + (avatarOrientation * fingerPosition);
return result;
}
// controllerWorldLocation is where the controller would be, in-world, with an added offset
static StylusTip getControllerWorldLocation(Side side, float sensorToWorldScale) {
static const std::array<Input, 2> INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel),
UserInputMapper::makeStandardInput(SIDES[1].channel) } };
const auto sideIndex = index(side);
const auto& input = INPUTS[sideIndex];
const auto pose = DependencyManager::get<UserInputMapper>()->getPose(input);
const auto& valid = pose.valid;
StylusTip result;
if (valid) {
result.side = side;
const auto& sideData = SIDES[sideIndex];
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint);
const auto avatarOrientation = myAvatar->getOrientation();
const auto avatarPosition = myAvatar->getPosition();
result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex);
result.position =
avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex));
// add to the real position so the grab-point is out in front of the hand, a bit
result.position += result.orientation * (sideData.grabPointSphereOffset * sensorToWorldScale);
auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation;
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
auto worldControllerLinearVel = avatarOrientation * pose.velocity;
auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity;
result.velocity =
worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos);
}
return result;
}
bool StylusPickResult::isNormalized() const {
return valid && (normalizedPosition == glm::clamp(normalizedPosition, vec3(0), vec3(1)));
}
bool StylusPickResult::isNearNormal(float min, float max, float hystersis) const {
return valid && (distance == glm::clamp(distance, min - hystersis, max + hystersis));
}
bool StylusPickResult::isNear2D(float border, float hystersis) const {
return valid && position2D == glm::clamp(position2D, vec2(0) - border - hystersis, vec2(dimensions) + border + hystersis);
}
bool StylusPickResult::isNear(float min, float max, float border, float hystersis) const {
// check to see if the projected stylusTip is within within the 2d border
return isNearNormal(min, max, hystersis) && isNear2D(border, hystersis);
}
StylusPickResult::operator bool() const {
return valid;
}
bool StylusPickResult::hasKeyboardFocus() const {
if (!overlayID.isNull()) {
return qApp->getOverlays().getKeyboardFocusOverlay() == overlayID;
}
#if 0
if (!entityID.isNull()) {
return qApp->getKeyboardFocusEntity() == entityID;
}
#endif
return false;
}
void StylusPickResult::setKeyboardFocus() const {
if (!overlayID.isNull()) {
qApp->getOverlays().setKeyboardFocusOverlay(overlayID);
qApp->setKeyboardFocusEntity(EntityItemID());
#if 0
} else if (!entityID.isNull()) {
qApp->getOverlays().setKeyboardFocusOverlay(OverlayID());
qApp->setKeyboardFocusEntity(entityID);
#endif
}
}
void StylusPickResult::sendHoverOverEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().hoverOverOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal });
}
// FIXME support entity
}
void StylusPickResult::sendHoverEnterEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().hoverEnterOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal });
}
// FIXME support entity
}
void StylusPickResult::sendTouchStartEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMousePressOnOverlay(overlayID, PointerEvent{ PointerEvent::Press, deviceId(), position2D, position,
normal, -normal, PointerEvent::PrimaryButton,
PointerEvent::PrimaryButton });
}
// FIXME support entity
}
void StylusPickResult::sendTouchEndEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMouseReleaseOnOverlay(overlayID,
PointerEvent{ PointerEvent::Release, deviceId(), position2D, position, normal,
-normal, PointerEvent::PrimaryButton });
}
// FIXME support entity
}
void StylusPickResult::sendTouchMoveEvent() const {
if (!overlayID.isNull()) {
qApp->getOverlays().sendMouseMoveOnOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position,
normal, -normal, PointerEvent::PrimaryButton,
PointerEvent::PrimaryButton });
}
// FIXME support entity
}
bool StylusPickResult::doesIntersect() const {
return true;
}
// for example: if we want the closest result, compare based on distance
// if we want all results, combine them
// must return a new pointer
std::shared_ptr<PickResult> StylusPickResult::compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) {
auto newStylusResult = std::static_pointer_cast<StylusPickResult>(newRes);
if (newStylusResult && newStylusResult->distance < distance) {
return std::make_shared<StylusPickResult>(*newStylusResult);
} else {
return std::make_shared<StylusPickResult>(*this);
}
}
// returns true if this result contains any valid results with distance < maxDistance
// can also filter out results with distance >= maxDistance
bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) {
return distance < maxDistance;
}
uint32_t StylusPickResult::deviceId() const {
// 0 is reserved for hardware mouse
return index(tip.side) + 1;
}
StylusPick::StylusPick(Side side)
: Pick(PickFilter(PickScriptingInterface::PICK_OVERLAYS()), FLT_MAX, true)
, _side(side) {
}
StylusTip StylusPick::getMathematicalPick() const {
StylusTip result;
if (_useFingerInsteadOfStylus) {
result = getFingerWorldLocation(_side);
} else {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
float sensorScaleFactor = myAvatar->getSensorToWorldScale();
result = getControllerWorldLocation(_side, sensorScaleFactor);
result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor);
}
return result;
}
PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const {
return std::make_shared<StylusPickResult>();
}
PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
return PickResultPointer();
}
PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) {
if (!getFilter().doesPickOverlays()) {
return PickResultPointer();
}
std::vector<StylusPickResult> results;
for (const auto& target : getIncludeItems()) {
if (target.isNull()) {
continue;
}
auto overlay = qApp->getOverlays().getOverlay(target);
// Don't interact with non-3D or invalid overlays
if (!overlay || !overlay->is3D()) {
continue;
}
if (!overlay->getVisible() && !getFilter().doesPickInvisible()) {
continue;
}
auto overlayType = overlay->getType();
auto overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
const auto overlayRotation = overlay3D->getRotation();
const auto overlayPosition = overlay3D->getPosition();
StylusPickResult result;
result.tip = pick;
result.overlayID = target;
result.normal = overlayRotation * Vectors::UNIT_Z;
result.distance = glm::dot(pick.position - overlayPosition, result.normal);
result.position = pick.position - (result.normal * result.distance);
if (overlayType == Web3DOverlay::TYPE) {
result.dimensions = vec3(std::static_pointer_cast<Web3DOverlay>(overlay3D)->getSize(), 0.01f);
} else if (overlayType == Sphere3DOverlay::TYPE) {
result.dimensions = std::static_pointer_cast<Sphere3DOverlay>(overlay3D)->getDimensions();
} else {
result.dimensions = vec3(0.01f);
}
auto tipRelativePosition = result.position - overlayPosition;
auto localPos = glm::inverse(overlayRotation) * tipRelativePosition;
auto normalizedPosition = localPos / result.dimensions;
result.normalizedPosition = normalizedPosition + 0.5f;
result.position2D = { result.normalizedPosition.x * result.dimensions.x,
(1.0f - result.normalizedPosition.y) * result.dimensions.y };
result.valid = true;
results.push_back(result);
}
StylusPickResult nearestTarget;
for (const auto& result : results) {
if (result && result.isNormalized() && result.distance < nearestTarget.distance) {
nearestTarget = result;
}
}
return std::make_shared<StylusPickResult>(nearestTarget);
}
PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) {
return PickResultPointer();
}
PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) {
return PickResultPointer();
}
StylusPointer::StylusPointer(Side side)
: Pointer(DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side)),
false,
true)
, _side(side)
, _sideData(SIDES[index(side)]) {
setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } });
STYLUSES[index(_side)] = this;
}
StylusPointer::~StylusPointer() {
if (!_stylusOverlay.isNull()) {
qApp->getOverlays().deleteOverlay(_stylusOverlay);
}
STYLUSES[index(_side)] = nullptr;
}
StylusPointer* StylusPointer::getOtherStylus() {
return STYLUSES[((index(_side) + 1) % 2)];
}
void StylusPointer::enable() {
Parent::enable();
withWriteLock([&] { _renderingEnabled = true; });
}
void StylusPointer::disable() {
Parent::disable();
withWriteLock([&] { _renderingEnabled = false; });
}
void StylusPointer::updateStylusTarget() {
const float minNearDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor;
const float maxNearDistance = WEB_DISPLAY_STYLUS_DISTANCE * _sensorScaleFactor;
const float edgeBorder = EDGE_BORDER * _sensorScaleFactor;
auto pickResult = DependencyManager::get<PickManager>()->getPrevPickResultTyped<StylusPickResult>(_pickUID);
if (pickResult) {
_state.target = *pickResult;
float hystersis = 0.0f;
// If we're already near the target, add hystersis to ensure we don't rapidly toggle between near and not near
// but only for the current near target
if (_previousState.nearTarget && pickResult->overlayID == _previousState.target.overlayID) {
hystersis = _nearHysteresis;
}
_state.nearTarget = pickResult->isNear(minNearDistance, maxNearDistance, edgeBorder, hystersis);
}
// Not near anything, short circuit the rest
if (!_state.nearTarget) {
relinquishTouchFocus();
hide();
return;
}
show();
auto minTouchDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor;
auto maxTouchDistance = TABLET_MAX_TOUCH_DISTANCE * _sensorScaleFactor;
auto maxHoverDistance = TABLET_MAX_HOVER_DISTANCE * _sensorScaleFactor;
float hystersis = 0.0f;
if (_previousState.nearTarget && _previousState.target.overlayID == _previousState.target.overlayID) {
hystersis = _nearHysteresis;
}
// If we're in hover distance (calculated as the normal distance from the XY plane of the overlay)
if ((getOtherStylus() && getOtherStylus()->_state.touchingTarget) || !_state.target.isNearNormal(minTouchDistance, maxHoverDistance, hystersis)) {
relinquishTouchFocus();
return;
}
requestTouchFocus(_state.target);
if (!_state.target.hasKeyboardFocus()) {
_state.target.setKeyboardFocus();
}
if (hasTouchFocus(_state.target) && !_previousState.touchingTarget) {
_state.target.sendHoverOverEvent();
}
hystersis = 0.0f;
if (_previousState.touchingTarget && _previousState.target.overlayID == _state.target.overlayID) {
hystersis = _touchHysteresis;
}
// If we're in touch distance
if (_state.target.isNearNormal(minTouchDistance, maxTouchDistance, _touchHysteresis) && _state.target.isNormalized()) {
_state.touchingTarget = true;
}
}
void StylusPointer::update(float deltaTime) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// Store and reset the state
{
_previousState = _state;
_state = State();
}
#if 0
// Update finger as stylus setting
{
useFingerInsteadOfStylus = (USE_FINGER_AS_STYLUS.get() && myAvatar->getJointIndex(sideData.avatarJoint) != -1);
}
#endif
// Update scale factor
{
_sensorScaleFactor = myAvatar->getSensorToWorldScale();
_hoverHysteresis = HOVER_HYSTERESIS * _sensorScaleFactor;
_nearHysteresis = NEAR_HYSTERESIS * _sensorScaleFactor;
_touchHysteresis = TOUCH_HYSTERESIS * _sensorScaleFactor;
}
// Identify the current near or touching target
updateStylusTarget();
// If we stopped touching, or if the target overlay ID changed, send a touching exit to the previous touch target
if (_previousState.touchingTarget &&
(!_state.touchingTarget || _state.target.overlayID != _previousState.target.overlayID)) {
stylusTouchingExit();
}
// Handle new or continuing touch
if (_state.touchingTarget) {
// If we were previously not touching, or we were touching a different overlay, add a touch enter
if (!_previousState.touchingTarget || _previousState.target.overlayID != _state.target.overlayID) {
stylusTouchingEnter();
} else {
_touchingEnterTimer += deltaTime;
}
stylusTouching();
}
});
setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } });
}
void StylusPointer::show() {
if (!_stylusOverlay.isNull()) {
return;
}
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
// FIXME perhaps instantiate a stylus and use show / hide instead of create / destroy
// however, the current design doesn't really allow for this because it assumes that
// hide / show are idempotent and low cost, but constantly querying the visibility
QVariantMap overlayProperties;
overlayProperties["name"] = "stylus";
overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx";
overlayProperties["loadPriority"] = 10.0f;
overlayProperties["dimensions"] = vec3toVariant(_sensorScaleFactor * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH));
overlayProperties["solid"] = true;
overlayProperties["visible"] = true;
overlayProperties["ignoreRayIntersection"] = true;
overlayProperties["drawInFront"] = false;
overlayProperties["parentID"] = AVATAR_SELF_ID;
overlayProperties["parentJointIndex"] = myAvatar->getJointIndex(_sideData.cameraJoint);
static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
auto modelOrientation = _state.target.tip.orientation * X_ROT_NEG_90;
auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * _sensorScaleFactor);
overlayProperties["position"] = vec3toVariant(_state.target.tip.position + modelPositionOffset);
overlayProperties["rotation"] = quatToVariant(modelOrientation);
_stylusOverlay = qApp->getOverlays().addOverlay("model", overlayProperties);
}
void StylusPointer::hide() {
if (_stylusOverlay.isNull()) {
return;
}
qApp->getOverlays().deleteOverlay(_stylusOverlay);
_stylusOverlay = OverlayID();
}
#if 0
void pointFinger(bool value) {
static const QString HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
static const std::array<QString, 2> KEYS{ { "pointLeftIndex", "pointLeftIndex" } };
if (fingerPointing != value) {
QString message = QJsonDocument(QJsonObject{ { KEYS[index(side)], value } }).toJson();
DependencyManager::get<MessagesClient>()->sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, message);
fingerPointing = value;
}
}
#endif
void StylusPointer::requestTouchFocus(const StylusPickResult& pickResult) {
if (!pickResult) {
return;
}
// send hover events to target if we can.
// record the entity or overlay we are hovering over.
if (!pickResult.overlayID.isNull() && pickResult.overlayID != _hoverOverlay &&
getOtherStylus() && pickResult.overlayID != getOtherStylus()->_hoverOverlay) {
_hoverOverlay = pickResult.overlayID;
pickResult.sendHoverEnterEvent();
}
}
bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) {
return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay);
}
void StylusPointer::relinquishTouchFocus() {
// send hover leave event.
if (!_hoverOverlay.isNull()) {
PointerEvent pointerEvent{ PointerEvent::Move, (uint32_t)(index(_side) + 1) };
auto& overlays = qApp->getOverlays();
overlays.sendMouseMoveOnOverlay(_hoverOverlay, pointerEvent);
overlays.sendHoverOverOverlay(_hoverOverlay, pointerEvent);
overlays.sendHoverLeaveOverlay(_hoverOverlay, pointerEvent);
_hoverOverlay = OverlayID();
}
};
void StylusPointer::stealTouchFocus() {
// send hover events to target
if (getOtherStylus() && _state.target.overlayID == getOtherStylus()->_hoverOverlay) {
getOtherStylus()->relinquishTouchFocus();
}
requestTouchFocus(_state.target);
}
void StylusPointer::stylusTouchingEnter() {
stealTouchFocus();
_state.target.sendTouchStartEvent();
DependencyManager::get<UserInputMapper>()->triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION,
_sideData.hand);
_touchingEnterTimer = 0;
_touchingEnterPosition = _state.target.position2D;
_deadspotExpired = false;
}
void StylusPointer::stylusTouchingExit() {
if (!_previousState.target) {
return;
}
// special case to handle home button.
if (_previousState.target.overlayID == getHomeButtonID()) {
DependencyManager::get<MessagesClient>()->sendLocalMessage("home", _previousState.target.overlayID.toString());
}
// send touch end event
_state.target.sendTouchEndEvent();
}
void StylusPointer::stylusTouching() {
qDebug() << "QQQ " << __FUNCTION__;
if (_state.target.overlayID.isNull()) {
return;
}
if (!_deadspotExpired) {
_deadspotExpired =
(_touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY) ||
glm::distance2(_state.target.position2D, _touchingEnterPosition) > TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED;
}
// Only send moves if the target moves more than the deadspot position or if we've timed out the deadspot
if (_deadspotExpired) {
_state.target.sendTouchMoveEvent();
}
}

View file

@ -0,0 +1,145 @@
//
// 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
//
#ifndef hifi_StylusPointer_h
#define hifi_StylusPointer_h
#include <QString>
#include <glm/glm.hpp>
#include <pointers/Pointer.h>
#include <pointers/Pick.h>
#include <shared/Bilateral.h>
#include <RegisteredMetaTypes.h>
#include <pointers/Pick.h>
#include "ui/overlays/Overlay.h"
class StylusPick : public Pick<StylusTip> {
using Side = bilateral::Side;
public:
StylusPick(Side side);
StylusTip getMathematicalPick() const override;
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override;
PickResultPointer getEntityIntersection(const StylusTip& pick) override;
PickResultPointer getOverlayIntersection(const StylusTip& pick) override;
PickResultPointer getAvatarIntersection(const StylusTip& pick) override;
PickResultPointer getHUDIntersection(const StylusTip& pick) override;
private:
const Side _side;
const bool _useFingerInsteadOfStylus{ false };
};
struct SideData;
struct StylusPickResult : public PickResult {
using Side = bilateral::Side;
// FIXME make into a single ID
OverlayID overlayID;
// FIXME restore entity functionality
#if 0
EntityItemID entityID;
#endif
StylusTip tip;
float distance{ FLT_MAX };
vec3 position;
vec2 position2D;
vec3 normal;
vec3 normalizedPosition;
vec3 dimensions;
bool valid{ false };
virtual bool doesIntersect() const override;
virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) override;
virtual bool checkOrFilterAgainstMaxDistance(float maxDistance) override;
bool isNormalized() const;
bool isNearNormal(float min, float max, float hystersis) const;
bool isNear2D(float border, float hystersis) const;
bool isNear(float min, float max, float border, float hystersis) const;
operator bool() const;
bool hasKeyboardFocus() const;
void setKeyboardFocus() const;
void sendHoverOverEvent() const;
void sendHoverEnterEvent() const;
void sendTouchStartEvent() const;
void sendTouchEndEvent() const;
void sendTouchMoveEvent() const;
private:
uint32_t deviceId() const;
};
class StylusPointer : public Pointer {
using Parent = Pointer;
using Side = bilateral::Side;
using Ptr = std::shared_ptr<StylusPointer>;
public:
StylusPointer(Side side);
~StylusPointer();
void enable() override;
void disable() override;
void update(float deltaTime) override;
private:
virtual void setRenderState(const std::string& state) override {}
virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {}
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) override { return PickedObject(); }
virtual Buttons getPressedButtons() override { return {}; }
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override { return PointerEvent(); }
StylusPointer* getOtherStylus();
void updateStylusTarget();
void requestTouchFocus(const StylusPickResult& pickResult);
bool hasTouchFocus(const StylusPickResult& pickResult);
void relinquishTouchFocus();
void stealTouchFocus();
void stylusTouchingEnter();
void stylusTouchingExit();
void stylusTouching();
void show();
void hide();
struct State {
StylusPickResult target;
bool nearTarget{ false };
bool touchingTarget{ false };
};
State _state;
State _previousState;
float _nearHysteresis{ 0.0f };
float _touchHysteresis{ 0.0f };
float _hoverHysteresis{ 0.0f };
float _sensorScaleFactor{ 1.0f };
float _touchingEnterTimer{ 0 };
vec2 _touchingEnterPosition;
bool _deadspotExpired{ false };
bool _renderingEnabled;
OverlayID _stylusOverlay;
OverlayID _hoverOverlay;
const Side _side;
const SideData& _sideData;
};
#endif // hifi_StylusPointer_h

View file

@ -118,7 +118,7 @@ public:
// for example: if we want the closest result, compare based on distance
// if we want all results, combine them
// must return a new pointer
virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult> newRes) = 0;
virtual std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) = 0;
// returns true if this result contains any valid results with distance < maxDistance
// can also filter out results with distance >= maxDistance
@ -198,7 +198,7 @@ class Pick : public PickQuery {
public:
Pick(const PickFilter& filter, const float maxDistance, const bool enabled) : PickQuery(filter, maxDistance, enabled) {}
virtual const T getMathematicalPick() const = 0;
virtual T getMathematicalPick() const = 0;
virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0;
virtual PickResultPointer getEntityIntersection(const T& pick) = 0;
virtual PickResultPointer getOverlayIntersection(const T& pick) = 0;

View file

@ -87,7 +87,9 @@ void PickCacheOptimizer<T>::update(QHash<QUuid, std::shared_ptr<PickQuery>>& pic
PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) {
PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick);
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
if (entityRes) {
cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick);
}
}
}
@ -95,7 +97,9 @@ void PickCacheOptimizer<T>::update(QHash<QUuid, std::shared_ptr<PickQuery>>& pic
PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) {
PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick);
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
if (overlayRes) {
cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick);
}
}
}
@ -103,7 +107,9 @@ void PickCacheOptimizer<T>::update(QHash<QUuid, std::shared_ptr<PickQuery>>& pic
PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) {
PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick);
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
if (avatarRes) {
cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick);
}
}
}
@ -112,7 +118,9 @@ void PickCacheOptimizer<T>::update(QHash<QUuid, std::shared_ptr<PickQuery>>& pic
PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector<QUuid>(), QVector<QUuid>() };
if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) {
PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick);
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
if (hudRes) {
cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick);
}
}
}

View file

@ -40,12 +40,12 @@ void PickManager::removePick(const QUuid& uid) {
});
}
QVariantMap PickManager::getPrevPickResult(const QUuid& uid) const {
PickResultPointer PickManager::getPrevPickResult(const QUuid& uid) const {
auto pick = findPick(uid);
if (pick && pick->getPrevPickResult()) {
return pick->getPrevPickResult()->toVariantMap();
if (pick) {
return pick->getPrevPickResult();
}
return QVariantMap();
return PickResultPointer();
}
void PickManager::enablePick(const QUuid& uid) const {
@ -91,4 +91,5 @@ void PickManager::update() {
bool shouldPickHUD = _shouldPickHUDOperator();
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD);
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false);
}

View file

@ -27,7 +27,12 @@ public:
void enablePick(const QUuid& uid) const;
void disablePick(const QUuid& uid) const;
QVariantMap getPrevPickResult(const QUuid& uid) const;
PickResultPointer getPrevPickResult(const QUuid& uid) const;
template <typename T>
std::shared_ptr<T> getPrevPickResultTyped(const QUuid& uid) const {
return std::static_pointer_cast<T>(getPrevPickResult(uid));
}
void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const;
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignore) const;
@ -43,6 +48,7 @@ protected:
QHash<QUuid, PickQuery::PickType> _typeMap;
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
};
#endif // hifi_PickManager_h

View file

@ -30,7 +30,7 @@ void Pointer::disable() {
DependencyManager::get<PickManager>()->disablePick(_pickUID);
}
const QVariantMap Pointer::getPrevPickResult() {
PickResultPointer Pointer::getPrevPickResult() {
return DependencyManager::get<PickManager>()->getPrevPickResult(_pickUID);
}
@ -46,16 +46,16 @@ void Pointer::setIncludeItems(const QVector<QUuid>& includeItems) const {
DependencyManager::get<PickManager>()->setIncludeItems(_pickUID, includeItems);
}
void Pointer::update() {
void Pointer::update(float deltaTime) {
// This only needs to be a read lock because update won't change any of the properties that can be modified from scripts
withReadLock([&] {
QVariantMap pickResult = getPrevPickResult();
auto pickResult = getPrevPickResult();
updateVisuals(pickResult);
generatePointerEvents(pickResult);
});
}
void Pointer::generatePointerEvents(const QVariantMap& pickResult) {
void Pointer::generatePointerEvents(const PickResultPointer& pickResult) {
// TODO: avatars/HUD?
auto pointerManager = DependencyManager::get<PointerManager>();
@ -176,4 +176,4 @@ PointerEvent::Button Pointer::chooseButton(const std::string& button) {
} else {
return PointerEvent::NoButtons;
}
}
}

View file

@ -16,6 +16,7 @@
#include <QVariant>
#include <shared/ReadWriteLockable.h>
#include "Pick.h"
#include <controllers/impl/Endpoint.h>
#include "PointerEvent.h"
@ -44,7 +45,7 @@ public:
virtual void enable();
virtual void disable();
virtual const QVariantMap getPrevPickResult();
virtual PickResultPointer getPrevPickResult();
virtual void setRenderState(const std::string& state) = 0;
virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0;
@ -57,13 +58,12 @@ public:
virtual void setLength(float length) {}
virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {}
void update();
virtual void updateVisuals(const QVariantMap& pickResult) = 0;
void generatePointerEvents(const QVariantMap& pickResult);
virtual void update(float deltaTime);
virtual void updateVisuals(const PickResultPointer& pickResult) {}
void generatePointerEvents(const PickResultPointer& pickResult);
struct PickedObject {
PickedObject() {}
PickedObject(const QUuid& objectID, IntersectionType type) : objectID(objectID), type(type) {}
PickedObject(const QUuid& objectID = QUuid(), IntersectionType type = IntersectionType::NONE) : objectID(objectID), type(type) {}
QUuid objectID;
IntersectionType type;
@ -71,7 +71,7 @@ public:
using Buttons = std::unordered_set<std::string>;
virtual PickedObject getHoveredObject(const QVariantMap& pickResult) = 0;
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0;
virtual Buttons getPressedButtons() = 0;
QUuid getRayUID() { return _pickUID; }
@ -81,7 +81,7 @@ protected:
bool _enabled;
bool _hover;
virtual PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const = 0;
virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const = 0;
private:
PickedObject _prevHoveredObject;

View file

@ -61,21 +61,22 @@ void PointerManager::editRenderState(const QUuid& uid, const std::string& state,
}
}
const QVariantMap PointerManager::getPrevPickResult(const QUuid& uid) const {
PickResultPointer PointerManager::getPrevPickResult(const QUuid& uid) const {
PickResultPointer result;
auto pointer = find(uid);
if (pointer) {
return pointer->getPrevPickResult();
result = pointer->getPrevPickResult();
}
return QVariantMap();
return result;
}
void PointerManager::update() {
void PointerManager::update(float deltaTime) {
auto cachedPointers = resultWithReadLock<QList<std::shared_ptr<Pointer>>>([&] {
return _pointers.values();
});
for (const auto& pointer : cachedPointers) {
pointer->update();
pointer->update(deltaTime);
}
}

View file

@ -29,7 +29,7 @@ public:
void disablePointer(const QUuid& uid) const;
void setRenderState(const QUuid& uid, const std::string& renderState) const;
void editRenderState(const QUuid& uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const;
const QVariantMap getPrevPickResult(const QUuid& uid) const;
PickResultPointer getPrevPickResult(const QUuid& uid) const;
void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const;
void setIgnoreItems(const QUuid& uid, const QVector<QUuid>& ignoreEntities) const;
@ -38,7 +38,7 @@ public:
void setLength(const QUuid& uid, float length) const;
void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const;
void update();
void update(float deltaTime);
private:
std::shared_ptr<Pointer> find(const QUuid& uid) const;

View file

@ -69,6 +69,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
#ifdef THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
forceDownload = true;
QUrl unnormalizedURL(scriptOrURL);
QUrl url = DependencyManager::get<ResourceManager>()->normalizeURL(unnormalizedURL);

View file

@ -37,9 +37,9 @@ public:
PointerEvent();
PointerEvent(EventType type, uint32_t id,
const glm::vec2& pos2D, const glm::vec3& pos3D,
const glm::vec3& normal, const glm::vec3& direction,
Button button, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier);
const glm::vec2& pos2D = glm::vec2(), const glm::vec3& pos3D = glm::vec3(),
const glm::vec3& normal = glm::vec3(), const glm::vec3& direction = glm::vec3(),
Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier);
static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event);
static void fromScriptValue(const QScriptValue& object, PointerEvent& event);

View file

@ -21,6 +21,7 @@
#include "AACube.h"
#include "SharedUtil.h"
#include "shared/Bilateral.h"
class QColor;
class QUrl;
@ -152,17 +153,73 @@ public:
return pickRay;
}
};
struct StylusTip : public MathPick {
bilateral::Side side{ bilateral::Side::Invalid };
glm::vec3 position;
glm::quat orientation;
glm::vec3 velocity;
virtual operator bool() const override { return side != bilateral::Side::Invalid; }
QVariantMap toVariantMap() const override {
QVariantMap pickRay;
pickRay["position"] = vec3toVariant(position);
pickRay["orientation"] = quatToVariant(orientation);
pickRay["velocity"] = vec3toVariant(velocity);
return pickRay;
}
};
namespace std {
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
template <>
struct hash<glm::vec3> {
size_t operator()(const glm::vec3& a) const {
return ((hash<float>()(a.x) ^ (hash<float>()(a.y) << 1)) >> 1) ^ (hash<float>()(a.z) << 1);
struct hash<bilateral::Side> {
size_t operator()(const bilateral::Side& a) const {
return std::hash<int>()((int)a);
}
};
template <>
template <>
struct hash<glm::vec3> {
size_t operator()(const glm::vec3& a) const {
size_t result = 0;
hash_combine(result, a.x, a.y, a.z);
return result;
}
};
template <>
struct hash<glm::quat> {
size_t operator()(const glm::quat& a) const {
size_t result = 0;
hash_combine(result, a.x, a.y, a.z, a.w);
return result;
}
};
template <>
struct hash<PickRay> {
size_t operator()(const PickRay& a) const {
return (hash<glm::vec3>()(a.origin) ^ (hash<glm::vec3>()(a.direction) << 1));
size_t result = 0;
hash_combine(result, a.origin, a.direction);
return result;
}
};
template <>
struct hash<StylusTip> {
size_t operator()(const StylusTip& a) const {
size_t result = 0;
hash_combine(result, a.side, a.position, a.orientation, a.velocity);
return result;
}
};
}

View file

@ -11,7 +11,8 @@
namespace bilateral {
enum class Side {
Left = 0,
Right = 1
Right = 1,
Invalid = -1
};
using Indices = Side;
@ -27,8 +28,10 @@ namespace bilateral {
return 0x01;
case Side::Right:
return 0x02;
default:
break;
}
return std::numeric_limits<uint8_t>::max();
return 0x00;
}
inline uint8_t index(Side side) {
@ -37,10 +40,24 @@ namespace bilateral {
return 0;
case Side::Right:
return 1;
default:
break;
}
return std::numeric_limits<uint8_t>::max();
}
inline Side side(int index) {
switch (index) {
case 0:
return Side::Left;
case 1:
return Side::Right;
default:
break;
}
return Side::Invalid;
}
template <typename F>
void for_each_side(F f) {
f(Side::Left);

View file

@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/tablet-ui/tabletUI.js"
];
var DEFAULT_SCRIPTS_SEPARATE = [
//"system/controllers/controllerScripts.js"
"system/controllers/controllerScripts.js"
// "system/chat.js"
];

View file

@ -16,256 +16,16 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js");
Script.include("/~/system/libraries/controllers.js");
(function() {
var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js");
// triggered when stylus presses a web overlay/entity
var HAPTIC_STYLUS_STRENGTH = 1.0;
var HAPTIC_STYLUS_DURATION = 20.0;
var WEB_DISPLAY_STYLUS_DISTANCE = 0.5;
var WEB_STYLUS_LENGTH = 0.2;
var WEB_TOUCH_Y_OFFSET = 0.105; // how far forward (or back with a negative number) to slide stylus in hand
function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) {
for (var i = 0; i < stylusTargets.length; i++) {
var stylusTarget = stylusTargets[i];
// check to see if the projected stylusTip is within within the 2d border
var borderMin = {x: -edgeBorder, y: -edgeBorder};
var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder};
if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance &&
stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y &&
stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) {
return true;
}
}
return false;
}
function calculateNearestStylusTarget(stylusTargets) {
var nearestStylusTarget;
for (var i = 0; i < stylusTargets.length; i++) {
var stylusTarget = stylusTargets[i];
if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) &&
stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 &&
stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) {
nearestStylusTarget = stylusTarget;
}
}
return nearestStylusTarget;
}
function getFingerWorldLocation(hand) {
var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4";
var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName);
var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex);
var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex);
var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation);
var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition));
return {
position: worldFingerPosition,
orientation: worldFingerRotation,
rotation: worldFingerRotation,
valid: true
};
}
function distance2D(a, b) {
var dx = (a.x - b.x);
var dy = (a.y - b.y);
return Math.sqrt(dx * dx + dy * dy);
}
function TabletStylusInput(hand) {
function TabletStylusInput(hand) {
this.hand = hand;
this.previousStylusTouchingTarget = false;
this.stylusTouchingTarget = false;
this.useFingerInsteadOfStylus = false;
this.fingerPointing = false;
// initialize stylus tip
var DEFAULT_STYLUS_TIP = {
position: {x: 0, y: 0, z: 0},
orientation: {x: 0, y: 0, z: 0, w: 0},
rotation: {x: 0, y: 0, z: 0, w: 0},
velocity: {x: 0, y: 0, z: 0},
valid: false
};
this.stylusTip = DEFAULT_STYLUS_TIP;
this.parameters = makeDispatcherModuleParameters(
100,
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
[],
100);
this.getOtherHandController = function() {
return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput;
};
this.handToController = function() {
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
};
this.updateFingerAsStylusSetting = function () {
var DEFAULT_USE_FINGER_AS_STYLUS = false;
var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus");
if (USE_FINGER_AS_STYLUS === "") {
USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS;
}
if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) {
this.useFingerInsteadOfStylus = true;
} else {
this.useFingerInsteadOfStylus = false;
}
};
this.updateStylusTip = function() {
if (this.useFingerInsteadOfStylus) {
this.stylusTip = getFingerWorldLocation(this.hand);
} else {
this.stylusTip = getControllerWorldLocation(this.handToController(), true);
// translate tip forward according to constant.
var TIP_OFFSET = Vec3.multiply(MyAvatar.sensorToWorldScale, {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0});
this.stylusTip.position = Vec3.sum(this.stylusTip.position,
Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET));
}
// compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions.
var pose = Controller.getPoseValue(this.handToController());
if (pose.valid) {
var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation));
var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity);
var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity);
var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel,
Vec3.subtract(this.stylusTip.position, worldControllerPos)));
this.stylusTip.velocity = tipVelocity;
} else {
this.stylusTip.velocity = {x: 0, y: 0, z: 0};
}
};
this.showStylus = function() {
if (this.stylus) {
var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 };
var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90);
var modelOrientationAngles = Quat.safeEulerAngles(modelOrientation);
var rotation = Overlays.getProperty(this.stylus, "rotation");
var rotationAngles = Quat.safeEulerAngles(rotation);
if(!Vec3.withinEpsilon(modelOrientationAngles, rotationAngles, 1)) {
var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 });
var updatedStylusProperties = {
position: Vec3.sum(this.stylusTip.position, modelPositionOffset),
rotation: modelOrientation,
dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }),
};
Overlays.editOverlay(this.stylus, updatedStylusProperties);
}
return;
}
var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 };
var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90);
var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 });
var stylusProperties = {
name: "stylus",
url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx",
loadPriority: 10.0,
position: Vec3.sum(this.stylusTip.position, modelPositionOffset),
rotation: modelOrientation,
dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }),
solid: true,
visible: true,
ignoreRayIntersection: true,
drawInFront: false,
parentID: MyAvatar.SELF_ID,
parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ?
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND")
};
this.stylus = Overlays.addOverlay("model", stylusProperties);
};
this.hideStylus = function() {
if (!this.stylus) {
return;
}
Overlays.deleteOverlay(this.stylus);
this.stylus = null;
};
this.stealTouchFocus = function(stylusTarget) {
// send hover events to target
// record the entity or overlay we are hovering over.
if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) ||
(stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) {
this.getOtherHandController().relinquishTouchFocus();
}
this.requestTouchFocus(stylusTarget);
};
this.requestTouchFocus = function(stylusTarget) {
// send hover events to target if we can.
// record the entity or overlay we are hovering over.
if (stylusTarget.entityID &&
stylusTarget.entityID !== this.hoverEntity &&
stylusTarget.entityID !== this.getOtherHandController().hoverEntity) {
this.hoverEntity = stylusTarget.entityID;
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget);
} else if (stylusTarget.overlayID &&
stylusTarget.overlayID !== this.hoverOverlay &&
stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) {
this.hoverOverlay = stylusTarget.overlayID;
TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget);
}
};
this.hasTouchFocus = function(stylusTarget) {
return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) ||
(stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay));
};
this.relinquishTouchFocus = function() {
// send hover leave event.
var pointerEvent = { type: "Move", id: this.hand + 1 };
if (this.hoverEntity) {
Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent);
this.hoverEntity = null;
} else if (this.hoverOverlay) {
Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent);
Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent);
Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent);
this.hoverOverlay = null;
}
};
this.pointFinger = function(value) {
var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index";
if (this.fingerPointing !== value) {
var message;
if (this.hand === RIGHT_HAND) {
message = { pointRightIndex: value };
} else {
message = { pointLeftIndex: value };
}
Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true);
this.fingerPointing = value;
}
};
this.pointer = Pointers.createPointer(PickType.Stylus, { hand: this.hand });
this.otherModuleNeedsToRun = function(controllerData) {
var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay";
@ -277,189 +37,6 @@ Script.include("/~/system/libraries/controllers.js");
return grabOverlayModuleReady.active || farGrabModuleReady.active;
};
this.processStylus = function(controllerData) {
this.updateStylusTip();
if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) {
this.pointFinger(false);
this.hideStylus();
this.stylusTouchingTarget = false;
this.relinquishTouchFocus();
return false;
}
if (this.useFingerInsteadOfStylus) {
this.hideStylus();
}
// build list of stylus targets, near the stylusTip
var stylusTargets = [];
var candidateEntities = controllerData.nearbyEntityProperties;
var i, props, stylusTarget;
for (i = 0; i < candidateEntities.length; i++) {
props = candidateEntities[i];
if (props && props.type === "Web") {
stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
}
// add the tabletScreen, if it is valid
if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL &&
Overlays.getProperty(HMD.tabletScreenID, "visible")) {
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
// add the tablet home button.
if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL &&
Overlays.getProperty(HMD.homeButtonID, "visible")) {
stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID);
if (stylusTarget) {
stylusTargets.push(stylusTarget);
}
}
var TABLET_MIN_HOVER_DISTANCE = 0.01;
var TABLET_MAX_HOVER_DISTANCE = 0.1;
var TABLET_MIN_TOUCH_DISTANCE = -0.05;
var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE;
var EDGE_BORDER = 0.075;
var hysteresisOffset = 0.0;
if (this.isNearStylusTarget) {
hysteresisOffset = 0.05;
}
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
this.isNearStylusTarget = isNearStylusTarget(stylusTargets,
(EDGE_BORDER + hysteresisOffset) * sensorScaleFactor,
(TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor,
(WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor);
if (this.isNearStylusTarget) {
if (!this.useFingerInsteadOfStylus) {
this.showStylus();
} else {
this.pointFinger(true);
}
} else {
this.hideStylus();
this.pointFinger(false);
}
var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets);
var SCALED_TABLET_MIN_TOUCH_DISTANCE = TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor;
var SCALED_TABLET_MAX_TOUCH_DISTANCE = TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor;
var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor;
if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) {
this.requestTouchFocus(nearestStylusTarget);
if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) {
TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget);
}
if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) {
TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget);
}
// filter out presses when tip is moving away from tablet.
// ensure that stylus is within bounding box by checking normalizedPosition
if (nearestStylusTarget.valid && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE &&
nearestStylusTarget.distance < SCALED_TABLET_MAX_TOUCH_DISTANCE &&
Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 &&
nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 &&
nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) {
this.stylusTarget = nearestStylusTarget;
this.stylusTouchingTarget = true;
}
} else {
this.relinquishTouchFocus();
}
this.homeButtonTouched = false;
if (this.isNearStylusTarget) {
return true;
} else {
this.pointFinger(false);
this.hideStylus();
return false;
}
};
this.stylusTouchingEnter = function () {
this.stealTouchFocus(this.stylusTarget);
TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.stylusTarget);
Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand);
this.touchingEnterTimer = 0;
this.touchingEnterStylusTarget = this.stylusTarget;
this.deadspotExpired = false;
var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481;
this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT;
};
this.stylusTouchingExit = function () {
if (this.stylusTarget === undefined) {
return;
}
// special case to handle home button.
if (this.stylusTarget.overlayID === HMD.homeButtonID) {
Messages.sendLocalMessage("home", this.stylusTarget.overlayID);
}
// send press event
if (this.deadspotExpired) {
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget);
} else {
TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.touchingEnterStylusTarget);
}
};
this.stylusTouching = function (controllerData, dt) {
this.touchingEnterTimer += dt;
if (this.stylusTarget.entityID) {
this.stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps);
} else if (this.stylusTarget.overlayID) {
this.stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID);
}
var TABLET_MIN_TOUCH_DISTANCE = -0.1;
var TABLET_MAX_TOUCH_DISTANCE = 0.01;
if (this.stylusTarget) {
if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE &&
this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) {
var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds
if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY ||
distance2D(this.stylusTarget.position2D,
this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) {
TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget);
this.deadspotExpired = true;
}
} else {
this.stylusTouchingTarget = false;
}
} else {
this.stylusTouchingTarget = false;
}
};
this.overlayLaserActive = function(controllerData) {
var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput");
var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput");
@ -469,7 +46,7 @@ Script.include("/~/system/libraries/controllers.js");
};
this.isReady = function (controllerData) {
if (this.processStylus(controllerData)) {
if (!this.overlayLaserActive(controllerData) && !this.otherModuleNeedsToRun(controllerData)) {
return makeRunningValues(true, [], []);
} else {
return makeRunningValues(false, [], []);
@ -477,28 +54,11 @@ Script.include("/~/system/libraries/controllers.js");
};
this.run = function (controllerData, deltaTime) {
this.updateFingerAsStylusSetting();
if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) {
this.stylusTouchingEnter();
}
if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) {
this.stylusTouchingExit();
}
this.previousStylusTouchingTarget = this.stylusTouchingTarget;
if (this.stylusTouchingTarget) {
this.stylusTouching(controllerData, deltaTime);
}
if (this.processStylus(controllerData)) {
return makeRunningValues(true, [], []);
} else {
return makeRunningValues(false, [], []);
}
return this.isReady(controllerData);
};
this.cleanup = function () {
this.hideStylus();
Pointers.createPointer(this.pointer);
};
}