mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 15:33:51 +02:00
integrated stylus into pointer system, need to update controller module
This commit is contained in:
parent
f970eb2302
commit
5a78c9ebfe
21 changed files with 645 additions and 782 deletions
interface/src/raypick
JointRayPick.cppJointRayPick.hLaserPointer.cppLaserPointer.hMouseRayPick.cppMouseRayPick.hPickScriptingInterface.cppPickScriptingInterface.hPointerScriptingInterface.cppRayPick.cppRayPick.hStaticRayPick.cppStaticRayPick.hStylusPick.cppStylusPick.hStylusPointer.cppStylusPointer.h
libraries
scripts/system/controllers/controllerModules
|
@ -12,7 +12,7 @@
|
|||
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance, const bool enabled) :
|
||||
JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
RayPick(filter, maxDistance, enabled),
|
||||
_jointName(jointName),
|
||||
_posOffset(posOffset),
|
||||
|
|
|
@ -16,7 +16,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);
|
||||
JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickRay getMathematicalPick() const override;
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ void LaserPointer::updateVisuals(const PickResultPointer& 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())) {
|
||||
PickRay pickRay{ rayPickResult->pickVariant };
|
||||
PickRay pickRay(rayPickResult->pickVariant);
|
||||
QUuid uid = rayPickResult->objectID;
|
||||
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
|
||||
updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false);
|
||||
|
@ -292,82 +292,30 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
|
|||
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const {
|
||||
QUuid pickedID;
|
||||
glm::vec3 intersection, surfaceNormal, direction, origin;
|
||||
if (target.type != NONE) {
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
|
||||
if (rayPickResult) {
|
||||
intersection = rayPickResult->intersection;
|
||||
surfaceNormal = rayPickResult->surfaceNormal;
|
||||
const QVariantMap& searchRay = rayPickResult->pickVariant;
|
||||
direction = vec3FromVariant(searchRay["direction"]);
|
||||
origin = vec3FromVariant(searchRay["origin"]);
|
||||
pickedID = rayPickResult->objectID;;
|
||||
pickedID = rayPickResult->objectID;
|
||||
}
|
||||
|
||||
glm::vec2 pos2D;
|
||||
if (pickedID != target.objectID) {
|
||||
if (target.type == ENTITY) {
|
||||
intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction);
|
||||
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
|
||||
} else if (target.type == OVERLAY) {
|
||||
intersection = intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
|
||||
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
|
||||
}
|
||||
}
|
||||
if (target.type == ENTITY) {
|
||||
pos2D = projectOntoEntityXYPlane(target.objectID, intersection);
|
||||
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, intersection);
|
||||
} else if (target.type == OVERLAY) {
|
||||
pos2D = projectOntoOverlayXYPlane(target.objectID, intersection);
|
||||
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, intersection);
|
||||
} else if (target.type == HUD) {
|
||||
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(intersection);
|
||||
}
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
|
||||
}
|
||||
|
||||
glm::vec3 LaserPointer::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const {
|
||||
glm::vec3 n = rotation * Vectors::FRONT;
|
||||
float t = glm::dot(n, point - origin) / glm::dot(n, direction);
|
||||
return origin + t * direction;
|
||||
}
|
||||
|
||||
glm::vec3 LaserPointer::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) const {
|
||||
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
|
||||
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
return intersectRayWithXYPlane(origin, direction, position, rotation, DEFAULT_REGISTRATION_POINT);
|
||||
}
|
||||
|
||||
glm::vec3 LaserPointer::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) const {
|
||||
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
|
||||
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
|
||||
}
|
||||
|
||||
glm::vec2 LaserPointer::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint) const {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
glm::vec3 invDimensions = glm::vec3(1.0f / dimensions.x, 1.0f / dimensions.y, 1.0f / dimensions.z);
|
||||
|
||||
glm::vec3 normalizedPos = (localPos * invDimensions) + registrationPoint;
|
||||
return glm::vec2(normalizedPos.x * dimensions.x, (1.0f - normalizedPos.y) * dimensions.y);
|
||||
}
|
||||
|
||||
glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos) const {
|
||||
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
|
||||
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
|
||||
glm::vec3 dimensions;
|
||||
|
||||
float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat();
|
||||
if (dpi > 0) {
|
||||
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale.
|
||||
glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1);
|
||||
glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f);
|
||||
const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
dimensions = (resolution * INCHES_TO_METERS / dpi) * scale;
|
||||
} else {
|
||||
dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01);
|
||||
}
|
||||
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -75,8 +75,8 @@ protected:
|
|||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Pointer::Buttons getPressedButtons() override;
|
||||
|
||||
bool shouldHover() override { return _currentRenderState != ""; }
|
||||
bool shouldTrigger() override { return _currentRenderState != ""; }
|
||||
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
|
||||
|
||||
private:
|
||||
PointerTriggers _triggers;
|
||||
|
@ -94,14 +94,6 @@ private:
|
|||
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay, bool defaultState);
|
||||
void disableRenderState(const RenderState& renderState);
|
||||
|
||||
|
||||
glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) const;
|
||||
glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) const;
|
||||
glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const;
|
||||
glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const;
|
||||
glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos) const;
|
||||
glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint) const;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_LaserPointer_h
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#include "Application.h"
|
||||
#include "display-plugins/CompositorHelper.h"
|
||||
|
||||
MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, const bool enabled) :
|
||||
MouseRayPick::MouseRayPick(const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
RayPick(filter, maxDistance, enabled)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
class MouseRayPick : public RayPick {
|
||||
|
||||
public:
|
||||
MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false);
|
||||
MouseRayPick(const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickRay getMathematicalPick() const override;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "StaticRayPick.h"
|
||||
#include "JointRayPick.h"
|
||||
#include "MouseRayPick.h"
|
||||
#include "StylusPick.h"
|
||||
|
||||
#include <pointers/Pick.h>
|
||||
#include <ScriptEngine.h>
|
||||
|
@ -24,6 +25,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
|
|||
switch (type) {
|
||||
case PickQuery::PickType::Ray:
|
||||
return createRayPick(properties);
|
||||
case PickQuery::PickType::Stylus:
|
||||
return createStylusPick(properties);
|
||||
default:
|
||||
return PickManager::INVALID_PICK_ID;
|
||||
}
|
||||
|
@ -81,6 +84,30 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
|||
return PickManager::INVALID_PICK_ID;
|
||||
}
|
||||
|
||||
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
|
||||
QVariantMap propMap = properties.toMap();
|
||||
|
||||
bilateral::Side side = bilateral::Side::Invalid;
|
||||
{
|
||||
QVariant handVar = propMap["hand"];
|
||||
if (handVar.isValid()) {
|
||||
side = bilateral::side(handVar.toInt());
|
||||
}
|
||||
}
|
||||
|
||||
bool enabled = false;
|
||||
if (propMap["enabled"].isValid()) {
|
||||
enabled = propMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(filter, side, enabled));
|
||||
}
|
||||
|
||||
void PickScriptingInterface::enablePick(unsigned int uid) {
|
||||
DependencyManager::get<PickManager>()->enablePick(uid);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ class PickScriptingInterface : public QObject, public Dependency {
|
|||
|
||||
public:
|
||||
unsigned int createRayPick(const QVariant& properties);
|
||||
unsigned int createStylusPick(const QVariant& properties);
|
||||
|
||||
void registerMetaTypes(QScriptEngine* engine);
|
||||
|
||||
|
|
|
@ -43,19 +43,19 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
|
|||
}
|
||||
|
||||
unsigned int 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());
|
||||
}
|
||||
QVariantMap propertyMap = properties.toMap();
|
||||
|
||||
bool hover = false;
|
||||
if (propertyMap["hover"].isValid()) {
|
||||
hover = propertyMap["hover"].toBool();
|
||||
}
|
||||
|
||||
if (bilateral::Side::Invalid == side) {
|
||||
return PointerEvent::INVALID_POINTER_ID;
|
||||
bool enabled = false;
|
||||
if (propertyMap["enabled"].isValid()) {
|
||||
enabled = propertyMap["enabled"].toBool();
|
||||
}
|
||||
|
||||
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(side));
|
||||
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<StylusPointer>(properties, StylusPointer::buildStylusOverlay(propertyMap), hover, enabled));
|
||||
}
|
||||
|
||||
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
|
||||
|
|
|
@ -48,4 +48,61 @@ PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
|
|||
PickResultPointer RayPick::getHUDIntersection(const PickRay& pick) {
|
||||
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateRayUICollisionPoint(pick.origin, pick.direction);
|
||||
return std::make_shared<RayPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick);
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 RayPick::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration) {
|
||||
// TODO: take into account registration
|
||||
glm::vec3 n = rotation * Vectors::FRONT;
|
||||
float t = glm::dot(n, point - origin) / glm::dot(n, direction);
|
||||
return origin + t * direction;
|
||||
}
|
||||
|
||||
glm::vec3 RayPick::intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction) {
|
||||
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
|
||||
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
return intersectRayWithXYPlane(origin, direction, position, rotation, DEFAULT_REGISTRATION_POINT);
|
||||
}
|
||||
|
||||
glm::vec3 RayPick::intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction) {
|
||||
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
|
||||
return intersectRayWithXYPlane(origin, direction, props.getPosition(), props.getRotation(), props.getRegistrationPoint());
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized) {
|
||||
glm::quat invRot = glm::inverse(rotation);
|
||||
glm::vec3 localPos = invRot * (worldPos - position);
|
||||
|
||||
glm::vec3 normalizedPos = (localPos / dimensions) + registrationPoint;
|
||||
|
||||
glm::vec2 pos2D = glm::vec2(normalizedPos.x, (1.0f - normalizedPos.y));
|
||||
if (unNormalized) {
|
||||
pos2D *= glm::vec2(dimensions.x, dimensions.y);
|
||||
}
|
||||
return pos2D;
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
|
||||
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
|
||||
glm::vec3 dimensions;
|
||||
|
||||
float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat();
|
||||
if (dpi > 0) {
|
||||
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale.
|
||||
glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1);
|
||||
glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f);
|
||||
const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
dimensions = (resolution * INCHES_TO_METERS / dpi) * scale;
|
||||
} else {
|
||||
dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01);
|
||||
}
|
||||
|
||||
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
|
||||
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT, unNormalized);
|
||||
}
|
||||
|
||||
glm::vec2 RayPick::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
|
||||
return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint(), unNormalized);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class RayPickResult : public PickResult {
|
|||
public:
|
||||
RayPickResult() {}
|
||||
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, const float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
|
||||
}
|
||||
|
||||
|
@ -67,13 +67,23 @@ public:
|
|||
class RayPick : public Pick<PickRay> {
|
||||
|
||||
public:
|
||||
RayPick(const PickFilter& filter, const float maxDistance, const bool enabled) : Pick(filter, maxDistance, enabled) {}
|
||||
RayPick(const PickFilter& filter, float maxDistance, bool enabled) : Pick(filter, maxDistance, enabled) {}
|
||||
|
||||
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared<RayPickResult>(pickVariant); }
|
||||
PickResultPointer getEntityIntersection(const PickRay& pick) override;
|
||||
PickResultPointer getOverlayIntersection(const PickRay& pick) override;
|
||||
PickResultPointer getAvatarIntersection(const PickRay& pick) override;
|
||||
PickResultPointer getHUDIntersection(const PickRay& pick) override;
|
||||
|
||||
// These are helper functions for projecting and intersecting rays
|
||||
static glm::vec3 intersectRayWithEntityXYPlane(const QUuid& entityID, const glm::vec3& origin, const glm::vec3& direction);
|
||||
static glm::vec3 intersectRayWithOverlayXYPlane(const QUuid& overlayID, const glm::vec3& origin, const glm::vec3& direction);
|
||||
static glm::vec2 projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos, bool unNormalized = true);
|
||||
static glm::vec2 projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized = true);
|
||||
|
||||
private:
|
||||
static glm::vec3 intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat& rotation, const glm::vec3& registration);
|
||||
static glm::vec2 projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3& position, const glm::quat& rotation, const glm::vec3& dimensions, const glm::vec3& registrationPoint, bool unNormalized);
|
||||
};
|
||||
|
||||
#endif // hifi_RayPick_h
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
#include "StaticRayPick.h"
|
||||
|
||||
StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance, const bool enabled) :
|
||||
StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance, bool enabled) :
|
||||
RayPick(filter, maxDistance, enabled),
|
||||
_pickRay(position, direction)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,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);
|
||||
StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
|
||||
|
||||
PickRay getMathematicalPick() const override;
|
||||
|
||||
|
|
230
interface/src/raypick/StylusPick.cpp
Normal file
230
interface/src/raypick/StylusPick.cpp
Normal file
|
@ -0,0 +1,230 @@
|
|||
//
|
||||
// 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 "StylusPick.h"
|
||||
|
||||
#include "RayPick.h"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "ui/overlays/Base3DOverlay.h"
|
||||
|
||||
#include "Application.h"
|
||||
#include <DependencyManager.h>
|
||||
#include "avatar/AvatarManager.h"
|
||||
|
||||
#include <controllers/StandardControls.h>
|
||||
#include <controllers/UserInputMapper.h>
|
||||
|
||||
using namespace bilateral;
|
||||
|
||||
// TODO: make these configurable per pick
|
||||
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 glm::vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f };
|
||||
|
||||
struct SideData {
|
||||
QString avatarJoint;
|
||||
QString cameraJoint;
|
||||
controller::StandardPoseChannel channel;
|
||||
controller::Hand hand;
|
||||
glm::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",
|
||||
controller::StandardPoseChannel::LEFT_HAND,
|
||||
controller::Hand::LEFT,
|
||||
{ -0.04f, 0.13f, 0.039f } },
|
||||
{ "RightHandIndex4",
|
||||
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND",
|
||||
controller::StandardPoseChannel::RIGHT_HAND,
|
||||
controller::Hand::RIGHT,
|
||||
{ 0.04f, 0.13f, 0.039f } } } };
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) {
|
||||
return distance < maxDistance;
|
||||
}
|
||||
|
||||
StylusPick::StylusPick(const PickFilter& filter, Side side, bool enabled) :
|
||||
Pick(filter, 0.0f, enabled),
|
||||
_side(side)
|
||||
{
|
||||
}
|
||||
|
||||
static StylusTip getFingerWorldLocation(Side side) {
|
||||
const auto& sideData = SIDES[index(side)];
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint);
|
||||
|
||||
if (fingerJointIndex == -1) {
|
||||
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) {
|
||||
static const std::array<controller::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();
|
||||
float sensorScaleFactor = myAvatar->getSensorToWorldScale();
|
||||
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 * sensorScaleFactor);
|
||||
// move the stylus forward a bit
|
||||
result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
StylusTip StylusPick::getMathematicalPick() const {
|
||||
StylusTip result;
|
||||
if (USE_FINGER_AS_STYLUS.get()) {
|
||||
result = getFingerWorldLocation(_side);
|
||||
} else {
|
||||
result = getControllerWorldLocation(_side);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const {
|
||||
return std::make_shared<StylusPickResult>(pickVariant);
|
||||
}
|
||||
|
||||
PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) {
|
||||
std::vector<StylusPickResult> results;
|
||||
for (const auto& target : getIncludeItems()) {
|
||||
if (target.isNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entity = qApp->getEntities()->getTree()->findEntityByEntityItemID(target);
|
||||
// Don't interact with non-3D or invalid overlays
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entity->getVisible() && !getFilter().doesPickInvisible()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto entityRotation = entity->getRotation();
|
||||
const auto entityPosition = entity->getPosition();
|
||||
|
||||
glm::vec3 normal = entityRotation * Vectors::UNIT_Z;
|
||||
float distance = glm::dot(pick.position - entityPosition, normal);
|
||||
glm::vec3 intersection = pick.position - (normal * distance);
|
||||
|
||||
glm::vec2 pos2D = RayPick::projectOntoEntityXYPlane(target, intersection, false);
|
||||
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
|
||||
results.push_back(StylusPickResult(IntersectionType::ENTITY, target, distance, intersection, pick, normal));
|
||||
}
|
||||
}
|
||||
|
||||
StylusPickResult nearestTarget(pick.toVariantMap());
|
||||
for (const auto& result : results) {
|
||||
if (result.distance < nearestTarget.distance) {
|
||||
nearestTarget = result;
|
||||
}
|
||||
}
|
||||
return std::make_shared<StylusPickResult>(nearestTarget);
|
||||
}
|
||||
|
||||
PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) {
|
||||
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 overlay3D = std::static_pointer_cast<Base3DOverlay>(overlay);
|
||||
const auto overlayRotation = overlay3D->getRotation();
|
||||
const auto overlayPosition = overlay3D->getPosition();
|
||||
|
||||
glm::vec3 normal = overlayRotation * Vectors::UNIT_Z;
|
||||
float distance = glm::dot(pick.position - overlayPosition, normal);
|
||||
glm::vec3 intersection = pick.position - (normal * distance);
|
||||
|
||||
glm::vec2 pos2D = RayPick::projectOntoOverlayXYPlane(target, intersection, false);
|
||||
if (pos2D == glm::clamp(pos2D, glm::vec2(0), glm::vec2(1))) {
|
||||
results.push_back(StylusPickResult(IntersectionType::OVERLAY, target, distance, intersection, pick, normal));
|
||||
}
|
||||
}
|
||||
|
||||
StylusPickResult nearestTarget(pick.toVariantMap());
|
||||
for (const auto& result : results) {
|
||||
if (result.distance < nearestTarget.distance) {
|
||||
nearestTarget = result;
|
||||
}
|
||||
}
|
||||
return std::make_shared<StylusPickResult>(nearestTarget);
|
||||
}
|
||||
|
||||
PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) {
|
||||
return std::make_shared<StylusPickResult>(pick.toVariantMap());
|
||||
}
|
||||
|
||||
PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) {
|
||||
return std::make_shared<StylusPickResult>(pick.toVariantMap());
|
||||
}
|
78
interface/src/raypick/StylusPick.h
Normal file
78
interface/src/raypick/StylusPick.h
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// 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_StylusPick_h
|
||||
#define hifi_StylusPick_h
|
||||
|
||||
#include "pointers/Pick.h"
|
||||
#include "RegisteredMetaTypes.h"
|
||||
|
||||
class StylusPickResult : public PickResult {
|
||||
using Side = bilateral::Side;
|
||||
|
||||
public:
|
||||
StylusPickResult() {}
|
||||
StylusPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||
StylusPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const StylusTip& stylusTip,
|
||||
const glm::vec3& surfaceNormal = glm::vec3(NAN)) :
|
||||
PickResult(stylusTip.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal) {
|
||||
}
|
||||
|
||||
StylusPickResult(const StylusPickResult& stylusPickResult) : PickResult(stylusPickResult.pickVariant) {
|
||||
type = stylusPickResult.type;
|
||||
intersects = stylusPickResult.intersects;
|
||||
objectID = stylusPickResult.objectID;
|
||||
distance = stylusPickResult.distance;
|
||||
intersection = stylusPickResult.intersection;
|
||||
surfaceNormal = stylusPickResult.surfaceNormal;
|
||||
}
|
||||
|
||||
IntersectionType type { NONE };
|
||||
bool intersects { false };
|
||||
QUuid objectID;
|
||||
float distance { FLT_MAX };
|
||||
glm::vec3 intersection { NAN };
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
|
||||
virtual QVariantMap toVariantMap() const override {
|
||||
QVariantMap toReturn;
|
||||
toReturn["type"] = type;
|
||||
toReturn["intersects"] = intersects;
|
||||
toReturn["objectID"] = objectID;
|
||||
toReturn["distance"] = distance;
|
||||
toReturn["intersection"] = vec3toVariant(intersection);
|
||||
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
|
||||
toReturn["stylusTip"] = PickResult::toVariantMap();
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
bool doesIntersect() const override { return intersects; }
|
||||
std::shared_ptr<PickResult> compareAndProcessNewResult(const std::shared_ptr<PickResult>& newRes) override;
|
||||
bool checkOrFilterAgainstMaxDistance(float maxDistance) override;
|
||||
};
|
||||
|
||||
class StylusPick : public Pick<StylusTip> {
|
||||
using Side = bilateral::Side;
|
||||
public:
|
||||
StylusPick(const PickFilter& filter, Side side, bool enabled);
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#endif // hifi_StylusPick_h
|
||||
|
||||
|
||||
|
||||
|
|
@ -7,621 +7,200 @@
|
|||
//
|
||||
#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 "RayPick.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 <DependencyManager.h>
|
||||
#include "PickScriptingInterface.h"
|
||||
#include <pointers/PickManager.h>
|
||||
|
||||
using namespace controller;
|
||||
using namespace bilateral;
|
||||
|
||||
static Setting::Handle<double> USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false);
|
||||
// TODO: make these configurable per pointer
|
||||
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_MIN_HOVER_DISTANCE = -0.1f;
|
||||
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 TABLET_MIN_TOUCH_DISTANCE = -0.1f;
|
||||
static const float TABLET_MAX_TOUCH_DISTANCE = 0.01f;
|
||||
|
||||
static const float HOVER_HYSTERESIS = 0.01f;
|
||||
static const float NEAR_HYSTERESIS = 0.05f;
|
||||
static const float TOUCH_HYSTERESIS = 0.002f;
|
||||
static const float TOUCH_HYSTERESIS = 0.02f;
|
||||
|
||||
// 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(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled) :
|
||||
Pointer(DependencyManager::get<PickScriptingInterface>()->createStylusPick(props), enabled, hover),
|
||||
_stylusOverlay(stylusOverlay)
|
||||
{
|
||||
}
|
||||
|
||||
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(unsigned int pointerID, 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
|
||||
OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) {
|
||||
QVariantMap overlayProperties;
|
||||
// TODO: make these configurable per pointer
|
||||
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["visible"] = false;
|
||||
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);
|
||||
return qApp->getOverlays().addOverlay("model", overlayProperties);
|
||||
}
|
||||
|
||||
void StylusPointer::updateVisuals(const PickResultPointer& pickResult) {
|
||||
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
|
||||
|
||||
if (_enabled && _renderState != DISABLED && stylusPickResult) {
|
||||
StylusTip tip(stylusPickResult->pickVariant);
|
||||
if (tip.side != bilateral::Side::Invalid) {
|
||||
show(tip);
|
||||
return;
|
||||
}
|
||||
}
|
||||
hide();
|
||||
}
|
||||
|
||||
void StylusPointer::show(const StylusTip& tip) {
|
||||
if (!_stylusOverlay.isNull()) {
|
||||
QVariantMap props;
|
||||
static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f };
|
||||
auto modelOrientation = tip.orientation * X_ROT_NEG_90;
|
||||
auto sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * sensorToWorldScale);
|
||||
props["position"] = vec3toVariant(tip.position + modelPositionOffset);
|
||||
props["rotation"] = quatToVariant(modelOrientation);
|
||||
props["dimensions"] = vec3toVariant(sensorToWorldScale * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH));
|
||||
props["visible"] = true;
|
||||
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
||||
}
|
||||
}
|
||||
|
||||
void StylusPointer::hide() {
|
||||
if (_stylusOverlay.isNull()) {
|
||||
return;
|
||||
if (!_stylusOverlay.isNull()) {
|
||||
QVariantMap props;
|
||||
props.insert("visible", false);
|
||||
qApp->getOverlays().editOverlay(_stylusOverlay, props);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
qApp->getOverlays().deleteOverlay(_stylusOverlay);
|
||||
_stylusOverlay = OverlayID();
|
||||
_state.hovering = false;
|
||||
return false;
|
||||
}
|
||||
#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;
|
||||
|
||||
bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) {
|
||||
auto stylusPickResult = std::static_pointer_cast<const StylusPickResult>(pickResult);
|
||||
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
|
||||
if (!_state.triggeredObject.objectID.isNull() && stylusPickResult->objectID != _state.triggeredObject.objectID) {
|
||||
glm::vec3 intersection;
|
||||
glm::vec3 origin = vec3FromVariant(stylusPickResult->pickVariant["position"]);
|
||||
glm::vec3 direction = -_state.surfaceNormal;
|
||||
if (_state.triggeredObject.type == ENTITY) {
|
||||
intersection = RayPick::intersectRayWithEntityXYPlane(_state.triggeredObject.objectID, origin, direction);
|
||||
} else if (_state.triggeredObject.type == OVERLAY) {
|
||||
intersection = RayPick::intersectRayWithOverlayXYPlane(_state.triggeredObject.objectID, origin, direction);
|
||||
}
|
||||
distance = glm::dot(intersection - 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)) {
|
||||
if (_state.triggeredObject.objectID.isNull()) {
|
||||
_state.triggeredObject = PickedObject(stylusPickResult->objectID, stylusPickResult->type);
|
||||
_state.surfaceNormal = stylusPickResult->surfaceNormal;
|
||||
_state.triggering = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#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();
|
||||
}
|
||||
|
||||
_state.triggeredObject = PickedObject();
|
||||
_state.surfaceNormal = glm::vec3(NAN);
|
||||
_state.triggering = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) {
|
||||
return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
Pointer::Buttons StylusPointer::getPressedButtons() {
|
||||
// TODO: custom buttons for styluses
|
||||
Pointer::Buttons toReturn({ "Primary", "Focus" });
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
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;
|
||||
PointerEvent StylusPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const {
|
||||
QUuid pickedID;
|
||||
glm::vec3 intersection, surfaceNormal, direction, origin;
|
||||
auto stylusPickResult = std::static_pointer_cast<StylusPickResult>(pickResult);
|
||||
if (stylusPickResult) {
|
||||
intersection = stylusPickResult->intersection;
|
||||
surfaceNormal = _state.surfaceNormal;
|
||||
const QVariantMap& stylusTip = stylusPickResult->pickVariant;
|
||||
origin = vec3FromVariant(stylusTip["position"]);
|
||||
direction = -_state.surfaceNormal;
|
||||
pickedID = stylusPickResult->objectID;
|
||||
}
|
||||
|
||||
if (pickedID != target.objectID) {
|
||||
if (target.type == ENTITY) {
|
||||
intersection = RayPick::intersectRayWithEntityXYPlane(target.objectID, origin, direction);
|
||||
} else if (target.type == OVERLAY) {
|
||||
intersection = RayPick::intersectRayWithOverlayXYPlane(target.objectID, origin, direction);
|
||||
}
|
||||
}
|
||||
glm::vec2 pos2D;
|
||||
if (target.type == ENTITY) {
|
||||
pos2D = RayPick::projectOntoEntityXYPlane(target.objectID, origin);
|
||||
} else if (target.type == OVERLAY) {
|
||||
pos2D = RayPick::projectOntoOverlayXYPlane(target.objectID, origin);
|
||||
} else if (target.type == HUD) {
|
||||
pos2D = DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
|
||||
}
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
|
||||
}
|
||||
|
||||
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();
|
||||
bool StylusPointer::isWithinBounds(float distance, float min, float max, float hysteresis) {
|
||||
return (distance == glm::clamp(distance, min - hysteresis, max + hysteresis));
|
||||
}
|
||||
|
||||
void StylusPointer::stylusTouching() {
|
||||
qDebug() << "QQQ " << __FUNCTION__;
|
||||
if (_state.target.overlayID.isNull()) {
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -8,136 +8,65 @@
|
|||
#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;
|
||||
};
|
||||
|
||||
#include "StylusPick.h"
|
||||
|
||||
class StylusPointer : public Pointer {
|
||||
using Parent = Pointer;
|
||||
using Side = bilateral::Side;
|
||||
using Ptr = std::shared_ptr<StylusPointer>;
|
||||
|
||||
public:
|
||||
StylusPointer(Side side);
|
||||
StylusPointer(const QVariant& props, const OverlayID& stylusOverlay, bool hover, bool enabled);
|
||||
~StylusPointer();
|
||||
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
void update(unsigned int pointerID, float deltaTime) override;
|
||||
void updateVisuals(const PickResultPointer& pickResult) override;
|
||||
|
||||
// Styluses have three render states:
|
||||
// default: "events on" -> render and hover/trigger
|
||||
// "events off" -> render, don't hover/trigger
|
||||
// "disabled" -> don't render, don't hover/trigger
|
||||
void setRenderState(const std::string& state) override;
|
||||
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {}
|
||||
|
||||
static OverlayID buildStylusOverlay(const QVariantMap& properties);
|
||||
|
||||
protected:
|
||||
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
|
||||
Buttons getPressedButtons() override;
|
||||
bool shouldHover(const PickResultPointer& pickResult) override;
|
||||
bool shouldTrigger(const PickResultPointer& pickResult) override;
|
||||
|
||||
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const 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 {}; }
|
||||
bool shouldHover() override { return true; }
|
||||
bool shouldTrigger() override { return true; }
|
||||
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 show(const StylusTip& tip);
|
||||
void hide();
|
||||
|
||||
struct State {
|
||||
StylusPickResult target;
|
||||
bool nearTarget{ false };
|
||||
bool touchingTarget{ false };
|
||||
struct TriggerState {
|
||||
PickedObject triggeredObject;
|
||||
glm::vec3 surfaceNormal { NAN };
|
||||
bool hovering { false };
|
||||
bool triggering { false };
|
||||
};
|
||||
|
||||
State _state;
|
||||
State _previousState;
|
||||
TriggerState _state;
|
||||
|
||||
float _nearHysteresis{ 0.0f };
|
||||
float _touchHysteresis{ 0.0f };
|
||||
float _hoverHysteresis{ 0.0f };
|
||||
enum RenderState {
|
||||
EVENTS_ON = 0,
|
||||
EVENTS_OFF,
|
||||
DISABLED
|
||||
};
|
||||
|
||||
float _sensorScaleFactor{ 1.0f };
|
||||
float _touchingEnterTimer{ 0 };
|
||||
vec2 _touchingEnterPosition;
|
||||
bool _deadspotExpired{ false };
|
||||
RenderState _renderState { EVENTS_ON };
|
||||
|
||||
bool _renderingEnabled;
|
||||
OverlayID _stylusOverlay;
|
||||
OverlayID _hoverOverlay;
|
||||
const Side _side;
|
||||
const SideData& _sideData;
|
||||
const OverlayID _stylusOverlay;
|
||||
|
||||
static bool isWithinBounds(float distance, float min, float max, float hysteresis);
|
||||
};
|
||||
|
||||
#endif // hifi_StylusPointer_h
|
||||
|
|
|
@ -78,7 +78,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
Buttons sameButtons;
|
||||
const std::string PRIMARY_BUTTON = "Primary";
|
||||
bool primaryPressed = false;
|
||||
if (_enabled && shouldTrigger()) {
|
||||
if (_enabled && shouldTrigger(pickResult)) {
|
||||
buttons = getPressedButtons();
|
||||
primaryPressed = buttons.find(PRIMARY_BUTTON) != buttons.end();
|
||||
for (const std::string& button : buttons) {
|
||||
|
@ -92,7 +92,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const PickResultPoin
|
|||
}
|
||||
|
||||
// Hover events
|
||||
bool doHover = shouldHover();
|
||||
bool doHover = shouldHover(pickResult);
|
||||
Pointer::PickedObject hoveredObject = getHoveredObject(pickResult);
|
||||
PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult);
|
||||
hoveredEvent.setType(PointerEvent::Move);
|
||||
|
|
|
@ -62,8 +62,8 @@ public:
|
|||
virtual void setLength(float length) {}
|
||||
virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {}
|
||||
|
||||
virtual void update(unsigned int pointerID, float deltaTime);
|
||||
virtual void updateVisuals(const PickResultPointer& pickResult) {}
|
||||
void update(unsigned int pointerID, float deltaTime);
|
||||
virtual void updateVisuals(const PickResultPointer& pickResult) = 0;
|
||||
void generatePointerEvents(unsigned int pointerID, const PickResultPointer& pickResult);
|
||||
|
||||
struct PickedObject {
|
||||
|
@ -87,8 +87,8 @@ protected:
|
|||
virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0;
|
||||
virtual Buttons getPressedButtons() = 0;
|
||||
|
||||
virtual bool shouldHover() = 0;
|
||||
virtual bool shouldTrigger() = 0;
|
||||
virtual bool shouldHover(const PickResultPointer& pickResult) { return true; }
|
||||
virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; }
|
||||
|
||||
private:
|
||||
PickedObject _prevHoveredObject;
|
||||
|
|
|
@ -154,18 +154,30 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
struct StylusTip : public MathPick {
|
||||
bilateral::Side side{ bilateral::Side::Invalid };
|
||||
class StylusTip : public MathPick {
|
||||
public:
|
||||
StylusTip() : position(NAN), velocity(NAN) {}
|
||||
StylusTip(const QVariantMap& pickVariant) : side(bilateral::Side(pickVariant["side"].toInt())), position(vec3FromVariant(pickVariant["position"])),
|
||||
orientation(quatFromVariant(pickVariant["orientation"])), velocity(vec3FromVariant(pickVariant["velocity"])) {}
|
||||
|
||||
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; }
|
||||
|
||||
operator bool() const override { return side != bilateral::Side::Invalid; }
|
||||
|
||||
bool operator==(const StylusTip& other) const {
|
||||
return (side == other.side && position == other.position && orientation == other.orientation && velocity == other.velocity);
|
||||
}
|
||||
|
||||
QVariantMap toVariantMap() const override {
|
||||
QVariantMap pickRay;
|
||||
pickRay["position"] = vec3toVariant(position);
|
||||
pickRay["orientation"] = quatToVariant(orientation);
|
||||
pickRay["velocity"] = vec3toVariant(velocity);
|
||||
return pickRay;
|
||||
QVariantMap stylusTip;
|
||||
stylusTip["side"] = (int)side;
|
||||
stylusTip["position"] = vec3toVariant(position);
|
||||
stylusTip["orientation"] = quatToVariant(orientation);
|
||||
stylusTip["velocity"] = vec3toVariant(velocity);
|
||||
return stylusTip;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -128,16 +128,16 @@ Script.include("/~/system/libraries/utils.js");
|
|||
LaserPointers.enableLaserPointer(this.laserPointer);
|
||||
LaserPointers.setRenderState(this.laserPointer, this.mode);
|
||||
|
||||
if (HMD.tabletID !== this.tabletID || HMD.tabletButtonID !== this.tabletButtonID || HMD.tabletScreenID !== this.tabletScreenID) {
|
||||
if (HMD.tabletID !== this.tabletID || HMD.homeButtonID !== this.homeButtonID || HMD.tabletScreenID !== this.tabletScreenID) {
|
||||
this.tabletID = HMD.tabletID;
|
||||
this.tabletButtonID = HMD.tabletButtonID;
|
||||
this.homeButtonID = HMD.homeButtonID;
|
||||
this.tabletScreenID = HMD.tabletScreenID;
|
||||
LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]);
|
||||
LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.homeButtonID, HMD.tabletScreenID]);
|
||||
}
|
||||
};
|
||||
|
||||
this.pointingAtTablet = function(objectID) {
|
||||
if (objectID === HMD.tabletScreenID || objectID === HMD.tabletButtonID) {
|
||||
if (objectID === HMD.tabletScreenID || objectID === HMD.homeButtonID) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -240,7 +240,7 @@ Script.include("/~/system/libraries/utils.js");
|
|||
defaultRenderStates: defaultRenderStates
|
||||
});
|
||||
|
||||
LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.tabletButtonID, HMD.tabletScreenID]);
|
||||
LaserPointers.setIgnoreItems(this.laserPointer, [HMD.tabletID, HMD.homeButtonID, HMD.tabletScreenID]);
|
||||
}
|
||||
|
||||
var leftHandInEditMode = new InEditMode(LEFT_HAND);
|
||||
|
|
Loading…
Reference in a new issue