refactoring common path pointer functionality

This commit is contained in:
SamGondelman 2018-07-17 16:15:20 -07:00
parent 845ddda695
commit 86c56195d3
6 changed files with 716 additions and 411 deletions

View file

@ -20,316 +20,106 @@
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
_triggers(triggers),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_followNormal(followNormal),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar)
PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal,
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
{
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second);
}
}
for (auto& state : _defaultRenderStates) {
if (!enabled || state.first != _currentRenderState) {
disableRenderState(state.second.second);
}
}
}
LaserPointer::~LaserPointer() {
for (auto& renderState : _renderStates) {
renderState.second.deleteOverlays();
}
for (auto& renderState : _defaultRenderStates) {
renderState.second.second.deleteOverlays();
}
}
void LaserPointer::setRenderState(const std::string& state) {
withWriteLock([&] {
if (!_currentRenderState.empty() && state != _currentRenderState) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
}
_currentRenderState = state;
});
}
void LaserPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
withWriteLock([&] {
updateRenderStateOverlay(_renderStates[state].getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state].getPathID(), pathProps);
updateRenderStateOverlay(_renderStates[state].getEndID(), endProps);
QVariant endDim = endProps.toMap()["dimensions"];
if (endDim.isValid()) {
_renderStates[state].setEndDim(vec3FromVariant(endDim));
}
void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {
updateRenderStateOverlay(renderState->getPathID(), pathProps);
QVariant lineWidth = pathProps.toMap()["lineWidth"];
if (lineWidth.isValid()) {
_renderStates[state].setLineWidth(lineWidth.toFloat());
}
QVariant rotation = pathProps.toMap()["rotation"];
if (rotation.isValid()) {
_renderStates[state].setEndRot(quatFromVariant(rotation));
}
});
}
PickResultPointer LaserPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
auto rayPickResult = std::static_pointer_cast<RayPickResult>(visualPickResult);
IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE;
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
glm::vec3 dim;
glm::vec3 registrationPoint;
if (_lockEndObject.isOverlay) {
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
registrationPoint = glm::vec3(0.5f);
} else {
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
pos = extractTranslation(finalPosAndRotMat);
rot = glmExtractRotation(finalPosAndRotMat);
dim = props.getDimensions();
registrationPoint = props.getRegistrationPoint();
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
rayPickResult->objectID = _lockEndObject.id;
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
} else if (type != IntersectionType::NONE && _lockEnd) {
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(rayPickResult->objectID)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(rayPickResult->objectID, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(rayPickResult->objectID)->getPosition();
}
glm::vec3 direction = endVec - pickRay.origin;
float distance = glm::distance(pickRay.origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
rayPickResult->intersection = endVec;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = -normalizedDirection;
rayPickResult->pickVariant["direction"] = vec3toVariant(normalizedDirection);
renderState->setLineWidth(lineWidth.toFloat());
}
}
return visualPickResult;
}
void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
glm::vec3 LaserPointer::getPickOrigin(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3());
}
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
if (distance > 0.0f) {
PickRay pick = PickRay(rayPickResult->pickVariant);
return pick.origin + distance * pick.direction;
} else {
return rayPickResult->intersection;
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
startProps.insert("visible", true);
startProps.insert("ignoreRayIntersection", renderState.doesStartIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
glm::vec3 endVec = pickRay.origin + pickRay.direction * distance;
QVariant end = vec3toVariant(endVec);
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("start", vec3toVariant(pickRay.origin));
pathProps.insert("end", end);
pathProps.insert("visible", true);
pathProps.insert("ignoreRayIntersection", renderState.doesPathIgnoreRays());
if (_scaleWithAvatar) {
pathProps.insert("lineWidth", renderState.getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
}
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(renderState.getEndID(), "dimensions").value);
if (_distanceScaleEnd) {
dim = renderState.getEndDim() * glm::distance(pickRay.origin, endVec);
endProps.insert("dimensions", vec3toVariant(dim));
}
if (_centerEndY) {
endProps.insert("position", end);
} else {
glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP;
endProps.insert("position", vec3toVariant(endVec + currentUpVector * vec3(0.0f, 0.5f * dim.y, 0.0f)));
}
if (_faceAvatar) {
glm::quat rotation = _faceAvatar ? faceAvatarRotation : renderState.getEndRot();
endProps.insert("rotation", quatToVariant(rotation));
}
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3());
}
void LaserPointer::disableRenderState(const RenderState& renderState) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("visible", false);
startProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getStartID(), startProps);
}
if (!renderState.getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("visible", false);
pathProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getPathID(), pathProps);
}
if (!renderState.getEndID().isNull()) {
QVariantMap endProps;
endProps.insert("visible", false);
endProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
IntersectionType LaserPointer::getPickedObjectType(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->type : IntersectionType::NONE);
}
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 || !_lockEndObject.id.isNull())) {
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant): PickRay();
QUuid uid = rayPickResult->objectID;
float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance;
updateRenderState(_renderStates[_currentRenderState], type, distance, rayPickResult->surfaceNormal, uid, pickRay);
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, Vectors::UP, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->objectID : QUuid());
}
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);
void LaserPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
rayPickResult->type = type;
rayPickResult->objectID = id;
rayPickResult->intersection = intersection;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = surfaceNormal;
rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal);
}
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
if (rayPickResult) {
for (const PointerTrigger& trigger : _triggers) {
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {
// start triggering for buttons that were just pressed
state.triggeredObject = PickedObject(rayPickResult->objectID, rayPickResult->type);
state.intersection = rayPickResult->intersection;
state.triggerPos2D = findPos2D(state.triggeredObject, rayPickResult->intersection);
state.triggerStartTime = usecTimestampNow();
state.surfaceNormal = rayPickResult->surfaceNormal;
state.deadspotExpired = false;
state.wasTriggering = true;
state.triggering = true;
_latestState = state;
}
} else {
// stop triggering for buttons that aren't pressed
state.wasTriggering = state.triggering;
state.triggering = false;
_latestState = state;
}
}
_previousButtons = toReturn;
}
return toReturn;
}
void LaserPointer::setLength(float length) {
withWriteLock([&] {
_laserLength = length;
});
}
void LaserPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
withWriteLock([&] {
_lockEndObject.id = objectID;
_lockEndObject.isOverlay = isOverlay;
_lockEndObject.offsetMat = offsetMat;
});
}
RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
_startID(startID), _pathID(pathID), _endID(endID)
LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID) :
StartEndRenderState(startID, endID), _pathID(pathID)
{
if (!_startID.isNull()) {
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
}
if (!_pathID.isNull()) {
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
_lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat();
}
if (!_endID.isNull()) {
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
_endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value);
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}
void RenderState::deleteOverlays() {
if (!_startID.isNull()) {
qApp->getOverlays().deleteOverlay(_startID);
}
void LaserPointer::RenderState::cleanup() {
StartEndRenderState::cleanup();
if (!_pathID.isNull()) {
qApp->getOverlays().deleteOverlay(_pathID);
}
if (!_endID.isNull()) {
qApp->getOverlays().deleteOverlay(_endID);
}
void LaserPointer::RenderState::disable() {
StartEndRenderState::disable();
if (!getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("visible", false);
pathProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getPathID(), pathProps);
}
}
RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) {
StartEndRenderState::update(origin, end, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal);
QVariant endVariant = vec3toVariant(end);
if (!getPathID().isNull()) {
QVariantMap pathProps;
pathProps.insert("start", vec3toVariant(origin));
pathProps.insert("end", endVariant);
pathProps.insert("visible", true);
pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays());
if (scaleWithAvatar) {
pathProps.insert("lineWidth", getLineWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale());
}
qApp->getOverlays().editOverlay(getPathID(), pathProps);
}
}
std::shared_ptr<StartEndRenderState> LaserPointer::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
@ -342,7 +132,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
QUuid pathID;
if (propMap["path"].isValid()) {
QVariantMap pathMap = propMap["path"].toMap();
// right now paths must be line3ds
// laser paths must be line3ds
if (pathMap["type"].isValid() && pathMap["type"].toString() == "line3d") {
pathMap.remove("visible");
pathID = qApp->getOverlays().addOverlay(pathMap["type"].toString(), pathMap);
@ -358,7 +148,7 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) {
}
}
return RenderState(startID, pathID, endID);
return std::make_shared<RenderState>(startID, pathID, endID);
}
PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
@ -406,16 +196,3 @@ glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const
return glm::vec3(NAN);
}
}
glm::vec2 LaserPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
}
}

View file

@ -11,122 +11,53 @@
#ifndef hifi_LaserPointer_h
#define hifi_LaserPointer_h
#include <QString>
#include <glm/glm.hpp>
#include "ui/overlays/Overlay.h"
#include <Pointer.h>
#include <Pick.h>
struct LockEndObject {
QUuid id { QUuid() };
bool isOverlay { false };
glm::mat4 offsetMat { glm::mat4() };
};
class RenderState {
#include "PathPointer.h"
class LaserPointer : public PathPointer {
using Parent = PathPointer;
public:
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
class RenderState : public StartEndRenderState {
public:
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& pathID, const OverlayID& endID);
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getPathID() const { return _pathID; }
const OverlayID& getEndID() const { return _endID; }
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
const OverlayID& getPathID() const { return _pathID; }
const bool& doesPathIgnoreRays() const { return _pathIgnoreRays; }
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
const glm::vec3& getEndDim() const { return _endDim; }
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
void setEndRot(const glm::quat& endRot) { _endRot = endRot; }
const glm::quat& getEndRot() const { return _endRot; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) override;
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
private:
OverlayID _pathID;
bool _pathIgnoreRays;
void deleteOverlays();
private:
OverlayID _startID;
OverlayID _pathID;
OverlayID _endID;
bool _startIgnoreRays;
bool _pathIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _endDim;
glm::quat _endRot;
float _lineWidth;
};
class LaserPointer : public Pointer {
using Parent = Pointer;
public:
typedef std::unordered_map<std::string, RenderState> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, RenderState>> DefaultRenderStateMap;
float _lineWidth;
};
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
~LaserPointer();
void setRenderState(const std::string& state) override;
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
static RenderState buildRenderState(const QVariantMap& propMap);
static std::shared_ptr<StartEndRenderState> buildRenderState(const QVariantMap& propMap);
protected:
void editRenderStatePath(const std::string& state, const QVariant& pathProps) override;
glm::vec3 getPickOrigin(const PickResultPointer& pickResult) override;
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) override;
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) override;
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) override;
QUuid getPickedObjectID(const PickResultPointer& pickResult) override;
void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) override;
PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
private:
PointerTriggers _triggers;
float _laserLength { 0.0f };
std::string _currentRenderState { "" };
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
bool _faceAvatar;
bool _followNormal;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
bool _scaleWithAvatar;
LockEndObject _lockEndObject;
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
void updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay);
void disableRenderState(const RenderState& renderState);
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
glm::vec2 triggerPos2D { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction);
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};

View file

@ -0,0 +1,323 @@
//
// Created by Sam Gondelman 7/17/2018
// Copyright 2018 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 "PathPointer.h"
#include "Application.h"
#include "avatar/AvatarManager.h"
#include <DependencyManager.h>
#include <PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
PathPointer::PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createPick(type, rayProps), enabled, hover),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_triggers(triggers),
_faceAvatar(faceAvatar),
_followNormal(followNormal),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar)
{
for (auto& state : _renderStates) {
if (!enabled || state.first != _currentRenderState) {
state.second->disable();
}
}
for (auto& state : _defaultRenderStates) {
if (!enabled || state.first != _currentRenderState) {
state.second.second->disable();
}
}
}
PathPointer::~PathPointer() {
for (auto& renderState : _renderStates) {
renderState.second->cleanup();
}
for (auto& renderState : _defaultRenderStates) {
renderState.second.second->cleanup();
}
}
void PathPointer::setRenderState(const std::string& state) {
withWriteLock([&] {
if (!_currentRenderState.empty() && state != _currentRenderState) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
}
_currentRenderState = state;
});
}
void PathPointer::setLength(float length) {
withWriteLock([&] {
_pathLength = length;
});
}
void PathPointer::setLockEndUUID(const QUuid& objectID, const bool isOverlay, const glm::mat4& offsetMat) {
withWriteLock([&] {
_lockEndObject.id = objectID;
_lockEndObject.isOverlay = isOverlay;
_lockEndObject.offsetMat = offsetMat;
});
}
PickResultPointer PathPointer::getVisualPickResult(const PickResultPointer& pickResult) {
PickResultPointer visualPickResult = pickResult;
glm::vec3 origin = getPickOrigin(pickResult);
IntersectionType type = getPickedObjectType(pickResult);
QUuid id;
glm::vec3 intersection;
float distance;
glm::vec3 surfaceNormal;
if (type != IntersectionType::HUD) {
glm::vec3 endVec;
if (!_lockEndObject.id.isNull()) {
glm::vec3 pos;
glm::quat rot;
glm::vec3 dim;
glm::vec3 registrationPoint;
if (_lockEndObject.isOverlay) {
pos = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "position").value);
rot = quatFromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "rotation").value);
dim = vec3FromVariant(qApp->getOverlays().getProperty(_lockEndObject.id, "dimensions").value);
registrationPoint = glm::vec3(0.5f);
} else {
EntityItemProperties props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(_lockEndObject.id);
glm::mat4 entityMat = createMatFromQuatAndPos(props.getRotation(), props.getPosition());
glm::mat4 finalPosAndRotMat = entityMat * _lockEndObject.offsetMat;
pos = extractTranslation(finalPosAndRotMat);
rot = glmExtractRotation(finalPosAndRotMat);
dim = props.getDimensions();
registrationPoint = props.getRegistrationPoint();
}
const glm::vec3 DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f);
endVec = pos + rot * (dim * (DEFAULT_REGISTRATION_POINT - registrationPoint));
glm::vec3 direction = endVec - origin;
float distance = glm::distance(origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
id = _lockEndObject.id;
intersection = endVec;
distance = distance;
surfaceNormal = -normalizedDirection;
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
} else if (type != IntersectionType::NONE && _lockEnd) {
id = getPickedObjectID(pickResult);
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(id)[3];
} else if (type == IntersectionType::OVERLAY) {
endVec = vec3FromVariant(qApp->getOverlays().getProperty(id, "position").value);
} else if (type == IntersectionType::AVATAR) {
endVec = DependencyManager::get<AvatarHashMap>()->getAvatar(id)->getPosition();
}
glm::vec3 direction = endVec - origin;
distance = glm::distance(origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
type = type;
intersection = endVec;
surfaceNormal = -normalizedDirection;
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
}
}
return visualPickResult;
}
void PathPointer::updateVisuals(const PickResultPointer& pickResult) {
IntersectionType type = getPickedObjectType(pickResult);
if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() &&
(type != IntersectionType::NONE || _pathLength > 0.0f || !_lockEndObject.id.isNull())) {
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, _pathLength);
_renderStates[_currentRenderState]->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal);
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, _defaultRenderStates[_currentRenderState].first);
_defaultRenderStates[_currentRenderState].second->update(origin, end, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar, _followNormal);
} else if (!_currentRenderState.empty()) {
if (_renderStates.find(_currentRenderState) != _renderStates.end()) {
_renderStates[_currentRenderState]->disable();
}
if (_defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
_defaultRenderStates[_currentRenderState].second->disable();
}
}
}
void PathPointer::editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) {
withWriteLock([&] {
updateRenderStateOverlay(_renderStates[state]->getStartID(), startProps);
updateRenderStateOverlay(_renderStates[state]->getEndID(), endProps);
QVariant startDim = startProps.toMap()["dimensions"];
if (startDim.isValid()) {
_renderStates[state]->setStartDim(vec3FromVariant(startDim));
}
QVariant endDim = endProps.toMap()["dimensions"];
if (endDim.isValid()) {
_renderStates[state]->setEndDim(vec3FromVariant(endDim));
}
QVariant rotation = pathProps.toMap()["rotation"];
if (rotation.isValid()) {
_renderStates[state]->setEndRot(quatFromVariant(rotation));
}
editRenderStatePath(state, pathProps);
});
}
void PathPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant& props) {
if (!id.isNull() && props.isValid()) {
QVariantMap propMap = props.toMap();
propMap.remove("visible");
qApp->getOverlays().editOverlay(id, propMap);
}
}
Pointer::PickedObject PathPointer::getHoveredObject(const PickResultPointer& pickResult) {
return PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
}
Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
for (const PointerTrigger& trigger : _triggers) {
std::string button = trigger.getButton();
TriggerState& state = _states[button];
// TODO: right now, LaserPointers don't support axes, only on/off buttons
if (trigger.getEndpoint()->peek() >= 1.0f) {
toReturn.insert(button);
if (_previousButtons.find(button) == _previousButtons.end()) {
// start triggering for buttons that were just pressed
state.triggeredObject = PickedObject(getPickedObjectID(pickResult), getPickedObjectType(pickResult));
state.intersection = getPickEnd(pickResult);
state.triggerPos2D = findPos2D(state.triggeredObject, state.intersection);
state.triggerStartTime = usecTimestampNow();
state.surfaceNormal = getPickedObjectNormal(pickResult);
state.deadspotExpired = false;
state.wasTriggering = true;
state.triggering = true;
_latestState = state;
}
} else {
// stop triggering for buttons that aren't pressed
state.wasTriggering = state.triggering;
state.triggering = false;
_latestState = state;
}
}
_previousButtons = toReturn;
return toReturn;
}
StartEndRenderState::StartEndRenderState(const OverlayID& startID, const OverlayID& endID) :
_startID(startID), _endID(endID) {
if (!_startID.isNull()) {
_startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value);
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
}
if (!_endID.isNull()) {
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
_endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value);
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
}
}
void StartEndRenderState::cleanup() {
if (!_startID.isNull()) {
qApp->getOverlays().deleteOverlay(_startID);
}
if (!_endID.isNull()) {
qApp->getOverlays().deleteOverlay(_endID);
}
}
void StartEndRenderState::disable() {
if (!getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("visible", false);
startProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getStartID(), startProps);
}
if (!getEndID().isNull()) {
QVariantMap endProps;
endProps.insert("visible", false);
endProps.insert("ignoreRayIntersection", true);
qApp->getOverlays().editOverlay(getEndID(), endProps);
}
}
void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal) {
if (!getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(origin));
startProps.insert("visible", true);
if (scaleWithAvatar) {
startProps.insert("dimensions", vec3toVariant(getStartDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale()));
}
startProps.insert("ignoreRayIntersection", doesStartIgnoreRays());
qApp->getOverlays().editOverlay(getStartID(), startProps);
}
if (!getEndID().isNull()) {
QVariantMap endProps;
glm::quat faceAvatarRotation = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, 180.0f, 0.0f)));
glm::vec3 dim = vec3FromVariant(qApp->getOverlays().getProperty(getEndID(), "dimensions").value);
if (distanceScaleEnd) {
dim = getEndDim() * glm::distance(origin, end);
endProps.insert("dimensions", vec3toVariant(dim));
} else if (scaleWithAvatar) {
dim = getEndDim() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
endProps.insert("dimensions", vec3toVariant(dim));
}
if (centerEndY) {
endProps.insert("position", vec3toVariant(end));
} else {
glm::vec3 currentUpVector = faceAvatarRotation * Vectors::UP;
endProps.insert("position", vec3toVariant(end + glm::vec3(currentUpVector.x * 0.5f * dim.y, currentUpVector.y * 0.5f * dim.y, currentUpVector.z * 0.5f * dim.y)));
}
if (faceAvatar) {
endProps.insert("rotation", quatToVariant(faceAvatarRotation));
}
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
qApp->getOverlays().editOverlay(getEndID(), endProps);
}
}
glm::vec2 PathPointer::findPos2D(const PickedObject& pickedObject, const glm::vec3& origin) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::projectOntoEntityXYPlane(pickedObject.objectID, origin);
case OVERLAY:
return RayPick::projectOntoOverlayXYPlane(pickedObject.objectID, origin);
case HUD:
return DependencyManager::get<PickManager>()->calculatePos2DFromHUD(origin);
default:
return glm::vec2(NAN);
}
}

View file

@ -0,0 +1,130 @@
//
// Created by Sam Gondelman 7/17/2018
// Copyright 2018 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_PathPointer_h
#define hifi_PathPointer_h
#include <QVariant>
#include <QString>
#include <glm/glm.hpp>
#include "ui/overlays/Overlay.h"
#include <Pointer.h>
#include <Pick.h>
struct LockEndObject {
QUuid id { QUuid() };
bool isOverlay { false };
glm::mat4 offsetMat { glm::mat4() };
};
class StartEndRenderState {
public:
StartEndRenderState() {}
StartEndRenderState(const OverlayID& startID, const OverlayID& endID);
const OverlayID& getStartID() const { return _startID; }
const OverlayID& getEndID() const { return _endID; }
const bool& doesStartIgnoreRays() const { return _startIgnoreRays; }
const bool& doesEndIgnoreRays() const { return _endIgnoreRays; }
void setStartDim(const glm::vec3& startDim) { _startDim = startDim; }
const glm::vec3& getStartDim() const { return _startDim; }
void setEndDim(const glm::vec3& endDim) { _endDim = endDim; }
const glm::vec3& getEndDim() const { return _endDim; }
void setEndRot(const glm::quat& endRot) { _endRot = endRot; }
const glm::quat& getEndRot() const { return _endRot; }
virtual void cleanup();
virtual void disable();
virtual void update(const glm::vec3& origin, const glm::vec3& end, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY, bool faceAvatar, bool followNormal);
protected:
OverlayID _startID;
OverlayID _endID;
bool _startIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _startDim;
glm::vec3 _endDim;
glm::quat _endRot;
};
typedef std::unordered_map<std::string, std::shared_ptr<StartEndRenderState>> RenderStateMap;
typedef std::unordered_map<std::string, std::pair<float, std::shared_ptr<StartEndRenderState>>> DefaultRenderStateMap;
class PathPointer : public Pointer {
using Parent = Pointer;
public:
PathPointer(PickQuery::PickType type, const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates,
bool hover, const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
virtual ~PathPointer();
void setRenderState(const std::string& state) override;
// You cannot use editRenderState to change the overlay type of any part of the laser pointer. You can only edit the properties of the existing overlays.
void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override;
void setLength(float length) override;
void setLockEndUUID(const QUuid& objectID, bool isOverlay, const glm::mat4& offsetMat = glm::mat4()) override;
void updateVisuals(const PickResultPointer& prevRayPickResult) override;
protected:
PointerTriggers _triggers;
float _pathLength { 0.0f };
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
std::string _currentRenderState { "" };
bool _faceAvatar;
bool _followNormal;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
bool _scaleWithAvatar;
LockEndObject _lockEndObject;
struct TriggerState {
PickedObject triggeredObject;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
glm::vec2 triggerPos2D { NAN };
quint64 triggerStartTime { 0 };
bool deadspotExpired { true };
bool triggering { false };
bool wasTriggering { false };
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
bool shouldHover(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
bool shouldTrigger(const PickResultPointer& pickResult) override { return _currentRenderState != ""; }
void updateRenderStateOverlay(const OverlayID& id, const QVariant& props);
virtual void editRenderStatePath(const std::string& state, const QVariant& pathProps) = 0;
PickedObject getHoveredObject(const PickResultPointer& pickResult) override;
Pointer::Buttons getPressedButtons(const PickResultPointer& pickResult) override;
PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) override;
virtual glm::vec3 getPickOrigin(const PickResultPointer& pickResult) = 0;
virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) = 0;
virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) = 0;
virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) = 0;
virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) = 0;
virtual void setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) = 0;
static glm::vec2 findPos2D(const PickedObject& pickedObject, const glm::vec3& origin);
};
#endif // hifi_PathPointer_h

View file

@ -37,6 +37,8 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType&
return createLaserPointer(properties);
case PickQuery::PickType::Stylus:
return createStylus(properties);
case PickQuery::PickType::Parabola:
return createParabolaPointer(properties);
default:
return PointerEvent::INVALID_POINTER_ID;
}
@ -84,27 +86,20 @@ unsigned int PointerScriptingInterface::createStylus(const QVariant& properties)
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Ray Pointer, if desired.
*/
/**jsdoc
* A trigger mechanism for Ray Pointers.
*
* @typedef {object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] Ray Pointers only. If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] Ray Pointers only. If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] Ray Pointers only. If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] Ray Pointers only. If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] Ray Pointers only. If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
* @property {boolean} [enabled=false]
* @property {Pointers.RayPointerRenderState[]} [renderStates] Ray Pointers only. A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] Ray Pointers only. A list of different visual states to use if there is no intersection.
* @property {Pointers.RayPointerRenderState[]} [renderStates] A list of different visual states to switch between.
* @property {Pointers.DefaultRayPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {Pointers.Trigger[]} [triggers] Ray Pointers only. A list of different triggers mechanisms that control this Pointer's click event generation.
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
*/
unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& properties) const {
QVariantMap propertyMap = properties.toMap();
@ -144,7 +139,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
enabled = propertyMap["enabled"].toBool();
}
LaserPointer::RenderStateMap renderStates;
RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
@ -158,7 +153,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
}
}
LaserPointer::DefaultRenderStateMap defaultRenderStates;
DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
@ -167,7 +162,7 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, RenderState>(distance, LaserPointer::buildRenderState(renderStateMap));
defaultRenderStates[name] = std::pair<float, std::shared_ptr<StartEndRenderState>>(distance, LaserPointer::buildRenderState(renderStateMap));
}
}
}
@ -201,6 +196,146 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
scaleWithAvatar, enabled));
}
/**jsdoc
* The rendering properties of the parabolic path
*
* @typedef {object} Pointers.ParabolaProperties
* @property {Color} color The color of the parabola.
* @property {number} alpha The alpha of the parabola.
* @property {number} width The width of the parabola, in meters.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is not intersecting something. Same as a {@link Pointers.ParabolaPointerRenderState},
* but with an additional distance field.
*
* @typedef {object} Pointers.DefaultParabolaPointerRenderState
* @augments Pointers.ParabolaPointerRenderState
* @property {number} distance The distance along the parabola at which to render the end of this Parabola Pointer, if one is defined.
*/
/**jsdoc
* A set of properties used to define the visual aspect of a Parabola Pointer in the case that the Pointer is intersecting something.
*
* @typedef {object} Pointers.ParabolaPointerRenderState
* @property {string} name The name of this render state, used by {@link Pointers.setRenderState} and {@link Pointers.editRenderState}
* @property {Overlays.OverlayProperties} [start] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the beginning of the Parabola Pointer, if desired.
* @property {Pointers.ParabolaProperties} [path] The rendering properties of the parabolic path defined by the Parabola Pointer.
* @property {Overlays.OverlayProperties} [end] All of the properties you would normally pass to {@link Overlays.addOverlay}, plus the type (as a <code>type</code> field).
* An overlay to represent the end of the Parabola Pointer, if desired.
*/
/**jsdoc
* A set of properties that can be passed to {@link Pointers.createPointer} to create a new Pointer. Contains the relevant {@link Picks.PickProperties} to define the underlying Pick.
* @typedef {object} Pointers.LaserPointerProperties
* @property {boolean} [faceAvatar=false] If true, the end of the Pointer will always rotate to face the avatar.
* @property {boolean} [centerEndY=true] If false, the end of the Pointer will be moved up by half of its height.
* @property {boolean} [lockEnd=false] If true, the end of the Pointer will lock on to the center of the object at which the laser is pointing.
* @property {boolean} [distanceScaleEnd=false] If true, the dimensions of the end of the Pointer will scale linearly with distance.
* @property {boolean} [scaleWithAvatar=false] If true, the width of the Pointer's path will scale linearly with your avatar's scale.
* @property {boolean} [followNormal=false] If true, the end of the Pointer will rotate to follow the normal of the intersected surface.
* @property {boolean} [enabled=false]
* @property {Pointers.ParabolaPointerRenderState[]} [renderStates] A list of different visual states to switch between.
* @property {Pointers.DefaultParabolaPointerRenderState[]} [defaultRenderStates] A list of different visual states to use if there is no intersection.
* @property {boolean} [hover=false] If this Pointer should generate hover events.
* @property {Pointers.Trigger[]} [triggers] A list of different triggers mechanisms that control this Pointer's click event generation.
*/
unsigned int PointerScriptingInterface::createParabolaPointer(const QVariant& properties) const {
return 0;
#if 0
QVariantMap propertyMap = properties.toMap();
bool faceAvatar = false;
if (propertyMap["faceAvatar"].isValid()) {
faceAvatar = propertyMap["faceAvatar"].toBool();
}
bool centerEndY = true;
if (propertyMap["centerEndY"].isValid()) {
centerEndY = propertyMap["centerEndY"].toBool();
}
bool lockEnd = false;
if (propertyMap["lockEnd"].isValid()) {
lockEnd = propertyMap["lockEnd"].toBool();
}
bool distanceScaleEnd = false;
if (propertyMap["distanceScaleEnd"].isValid()) {
distanceScaleEnd = propertyMap["distanceScaleEnd"].toBool();
}
bool scaleWithAvatar = false;
if (propertyMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool followNormal = true;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
ParabolaPointer::RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
renderStates[name] = ParabolaPointer::buildRenderState(renderStateMap);
}
}
}
}
ParabolaPointer::DefaultRenderStateMap defaultRenderStates;
if (propertyMap["defaultRenderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["defaultRenderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
if (renderStateVariant.isValid()) {
QVariantMap renderStateMap = renderStateVariant.toMap();
if (renderStateMap["name"].isValid() && renderStateMap["distance"].isValid()) {
std::string name = renderStateMap["name"].toString().toStdString();
float distance = renderStateMap["distance"].toFloat();
defaultRenderStates[name] = std::pair<float, ParabolaPointer::RenderState>(distance, ParabolaPointer::buildRenderState(renderStateMap));
}
}
}
}
bool hover = false;
if (propertyMap["hover"].isValid()) {
hover = propertyMap["hover"].toBool();
}
PointerTriggers triggers;
auto userInputMapper = DependencyManager::get<UserInputMapper>();
if (propertyMap["triggers"].isValid()) {
QList<QVariant> triggerVariants = propertyMap["triggers"].toList();
for (const QVariant& triggerVariant : triggerVariants) {
if (triggerVariant.isValid()) {
QVariantMap triggerMap = triggerVariant.toMap();
if (triggerMap["action"].isValid() && triggerMap["button"].isValid()) {
controller::Endpoint::Pointer endpoint = userInputMapper->endpointFor(controller::Input(triggerMap["action"].toUInt()));
if (endpoint) {
std::string button = triggerMap["button"].toString().toStdString();
triggers.emplace_back(endpoint, button);
}
}
}
}
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<ParabolaPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
faceAvatar, followNormal, centerEndY, lockEnd, distanceScaleEnd,
scaleWithAvatar, enabled));
#endif
}
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {
QVariantMap propMap = properties.toMap();

View file

@ -31,14 +31,23 @@ class PointerScriptingInterface : public QObject, public Dependency {
public:
unsigned int createLaserPointer(const QVariant& properties) const;
unsigned int createStylus(const QVariant& properties) const;
unsigned int createParabolaPointer(const QVariant& properties) const;
/**jsdoc
* A trigger mechanism for Ray and Parabola Pointers.
*
* @typedef {object} Pointers.Trigger
* @property {Controller.Standard|Controller.Actions|function} action This can be a built-in Controller action, like Controller.Standard.LTClick, or a function that evaluates to >= 1.0 when you want to trigger <code>button</code>.
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
*/
/**jsdoc
* Adds a new Pointer
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
* @function Pointers.createPointer
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
* this Pointer will use to do its picking.
* @returns {number} The ID of the created Pointer. Used for managing the Pointer. 0 if invalid.
*