Merge pull request #13660 from SamGondelman/parabolic

Parabolic Picking + Pointers
This commit is contained in:
Sam Gondelman 2018-07-30 10:49:02 -07:00 committed by GitHub
commit 8201997a16
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
104 changed files with 4343 additions and 1034 deletions

View file

@ -5291,7 +5291,7 @@ void Application::setKeyboardFocusHighlight(const glm::vec3& position, const glm
_keyboardFocusHighlight->setPulseMin(0.5);
_keyboardFocusHighlight->setPulseMax(1.0);
_keyboardFocusHighlight->setColorPulse(1.0);
_keyboardFocusHighlight->setIgnoreRayIntersection(true);
_keyboardFocusHighlight->setIgnorePickIntersection(true);
_keyboardFocusHighlight->setDrawInFront(false);
_keyboardFocusHighlightID = getOverlays().addOverlay(_keyboardFocusHighlight);
}

View file

@ -618,6 +618,8 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
result.intersects = true;
result.avatarID = avatar->getID();
result.distance = distance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
}
}
@ -629,6 +631,79 @@ RayToAvatarIntersectionResult AvatarManager::findRayIntersectionVector(const Pic
return result;
}
ParabolaToAvatarIntersectionResult AvatarManager::findParabolaIntersectionVector(const PickParabola& pick,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard) {
ParabolaToAvatarIntersectionResult result;
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(const_cast<AvatarManager*>(this), "findParabolaIntersectionVector",
Q_RETURN_ARG(ParabolaToAvatarIntersectionResult, result),
Q_ARG(const PickParabola&, pick),
Q_ARG(const QVector<EntityItemID>&, avatarsToInclude),
Q_ARG(const QVector<EntityItemID>&, avatarsToDiscard));
return result;
}
auto avatarHashCopy = getHashCopy();
for (auto avatarData : avatarHashCopy) {
auto avatar = std::static_pointer_cast<Avatar>(avatarData);
if ((avatarsToInclude.size() > 0 && !avatarsToInclude.contains(avatar->getID())) ||
(avatarsToDiscard.size() > 0 && avatarsToDiscard.contains(avatar->getID()))) {
continue;
}
float parabolicDistance;
BoxFace face;
glm::vec3 surfaceNormal;
SkeletonModelPointer avatarModel = avatar->getSkeletonModel();
// It's better to intersect the parabola against the avatar's actual mesh, but this is currently difficult to
// do, because the transformed mesh data only exists over in GPU-land. As a compromise, this code
// intersects against the avatars capsule and then against the (T-pose) mesh. The end effect is that picking
// against the avatar is sort-of right, but you likely wont be able to pick against the arms.
// TODO -- find a way to extract transformed avatar mesh data from the rendering engine.
// if we weren't picking against the capsule, we would want to pick against the avatarBounds...
// AABox avatarBounds = avatarModel->getRenderableMeshBound();
// if (!avatarBounds.findParabolaIntersection(pick.origin, pick.velocity, pick.acceleration, parabolicDistance, face, surfaceNormal)) {
// // parabola doesn't intersect avatar's bounding-box
// continue;
// }
glm::vec3 start;
glm::vec3 end;
float radius;
avatar->getCapsule(start, end, radius);
bool intersects = findParabolaCapsuleIntersection(pick.origin, pick.velocity, pick.acceleration, start, end, radius, avatar->getWorldOrientation(), parabolicDistance);
if (!intersects) {
// ray doesn't intersect avatar's capsule
continue;
}
QVariantMap extraInfo;
intersects = avatarModel->findParabolaIntersectionAgainstSubMeshes(pick.origin, pick.velocity, pick.acceleration,
parabolicDistance, face, surfaceNormal, extraInfo, true);
if (intersects && (!result.intersects || parabolicDistance < result.parabolicDistance)) {
result.intersects = true;
result.avatarID = avatar->getID();
result.parabolicDistance = parabolicDistance;
result.face = face;
result.surfaceNormal = surfaceNormal;
result.extraInfo = extraInfo;
}
}
if (result.intersects) {
result.intersection = pick.origin + pick.velocity * result.parabolicDistance + 0.5f * pick.acceleration * result.parabolicDistance * result.parabolicDistance;
result.distance = glm::distance(pick.origin, result.intersection);
}
return result;
}
// HACK
float AvatarManager::getAvatarSortCoefficient(const QString& name) {
if (name == "size") {

View file

@ -142,6 +142,10 @@ public:
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick,
const QVector<EntityItemID>& avatarsToInclude,
const QVector<EntityItemID>& avatarsToDiscard);
/**jsdoc
* @function AvatarManager.getAvatarSortCoefficient
* @param {string} name

View file

@ -48,7 +48,7 @@ void OtherAvatar::createOrb() {
_otherAvatarOrbMeshPlaceholder->setPulseMin(0.5);
_otherAvatarOrbMeshPlaceholder->setPulseMax(1.0);
_otherAvatarOrbMeshPlaceholder->setColorPulse(1.0);
_otherAvatarOrbMeshPlaceholder->setIgnoreRayIntersection(true);
_otherAvatarOrbMeshPlaceholder->setIgnorePickIntersection(true);
_otherAvatarOrbMeshPlaceholder->setDrawInFront(false);
_otherAvatarOrbMeshPlaceholderID = qApp->getOverlays().addOverlay(_otherAvatarOrbMeshPlaceholder);
// Position focus

View file

@ -0,0 +1,43 @@
//
// Created by Sam Gondelman 7/2/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 "JointParabolaPick.h"
#include "avatar/AvatarManager.h"
JointParabolaPick::JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
_jointName(jointName),
_posOffset(posOffset),
_dirOffset(dirOffset)
{
}
PickParabola JointParabolaPick::getMathematicalPick() const {
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName));
bool useAvatarHead = _jointName == "Avatar";
const int INVALID_JOINT = -1;
if (jointIndex != INVALID_JOINT || useAvatarHead) {
glm::vec3 jointPos = useAvatarHead ? myAvatar->getHeadPosition() : myAvatar->getAbsoluteJointTranslationInObjectFrame(jointIndex);
glm::quat jointRot = useAvatarHead ? myAvatar->getHeadOrientation() : myAvatar->getAbsoluteJointRotationInObjectFrame(jointIndex);
glm::vec3 avatarPos = myAvatar->getWorldPosition();
glm::quat avatarRot = myAvatar->getWorldOrientation();
glm::vec3 pos = useAvatarHead ? jointPos : avatarPos + (avatarRot * jointPos);
glm::quat rot = useAvatarHead ? jointRot * glm::angleAxis(-PI / 2.0f, Vectors::RIGHT) : avatarRot * jointRot;
// Apply offset
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
return PickParabola(pos, getSpeed() * dir, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,32 @@
//
// Created by Sam Gondelman 7/2/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_JointParabolaPick_h
#define hifi_JointParabolaPick_h
#include "ParabolaPick.h"
class JointParabolaPick : public ParabolaPick {
public:
JointParabolaPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset,
float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); }
bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); }
private:
std::string _jointName;
glm::vec3 _posOffset;
glm::vec3 _dirOffset;
};
#endif // hifi_JointParabolaPick_h

View file

@ -36,7 +36,7 @@ PickRay JointRayPick::getMathematicalPick() const {
// Apply offset
pos = pos + (rot * (myAvatar->getSensorToWorldScale() * _posOffset));
glm::vec3 dir = rot * glm::normalize(_dirOffset);
glm::vec3 dir = glm::normalize(rot * glm::normalize(_dirOffset));
return PickRay(pos, dir);
}

View file

@ -14,315 +14,114 @@
#include "avatar/AvatarManager.h"
#include <DependencyManager.h>
#include <PickManager.h>
#include "PickScriptingInterface.h"
#include "RayPick.h"
LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
_triggers(triggers),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
_scaleWithAvatar(scaleWithAvatar)
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalTime, bool centerEndY, bool lockEnd,
bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
PathPointer(PickQuery::Ray, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalTime,
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());
}
});
}
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) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? vec3FromVariant(rayPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
}
glm::vec3 LaserPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
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 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 + 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", renderState.doesEndIgnoreRays());
qApp->getOverlays().editOverlay(renderState.getEndID(), endProps);
}
glm::vec3 LaserPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
auto rayPickResult = std::static_pointer_cast<RayPickResult>(pickResult);
return (rayPickResult ? rayPickResult->surfaceNormal : glm::vec3(0.0f));
}
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) const {
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, 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, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
}
QUuid LaserPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
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);
}
Pointer::Buttons LaserPointer::getPressedButtons(const PickResultPointer& pickResult) {
std::unordered_set<std::string> toReturn;
auto rayPickResult = std::static_pointer_cast<const RayPickResult>(pickResult);
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);
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;
rayPickResult->type = type;
rayPickResult->objectID = id;
rayPickResult->intersection = intersection;
rayPickResult->distance = distance;
rayPickResult->surfaceNormal = surfaceNormal;
rayPickResult->pickVariant["direction"] = vec3toVariant(-surfaceNormal);
}
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);
_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, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
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();
@ -335,7 +134,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);
@ -351,7 +150,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) {
@ -391,24 +190,11 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
glm::vec3 LaserPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& direction) {
switch (pickedObject.type) {
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
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);
case ENTITY:
return RayPick::intersectRayWithEntityXYPlane(pickedObject.objectID, origin, direction);
case OVERLAY:
return RayPick::intersectRayWithOverlayXYPlane(pickedObject.objectID, origin, direction);
default:
return glm::vec3(NAN);
}
}

View file

@ -11,117 +11,54 @@
#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 setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
void deleteOverlays();
private:
OverlayID _pathID;
bool _pathIgnoreRays;
private:
OverlayID _startID;
OverlayID _pathID;
OverlayID _endID;
bool _startIgnoreRays;
bool _pathIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _endDim;
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;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, 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);
protected:
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 _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 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 };
float _lineWidth;
};
Pointer::Buttons _previousButtons;
std::unordered_map<std::string, TriggerState> _states;
TriggerState _latestState;
LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
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) const override;
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
QUuid getPickedObjectID(const PickResultPointer& pickResult) const 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;
private:
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,28 @@
//
// Created by Sam Gondelman 7/2/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 "MouseParabolaPick.h"
#include "Application.h"
#include "display-plugins/CompositorHelper.h"
MouseParabolaPick::MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled)
{
}
PickParabola MouseParabolaPick::getMathematicalPick() const {
QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition();
if (position.isValid()) {
QVariantMap posMap = position.toMap();
PickRay pickRay = qApp->getCamera().computePickRay(posMap["x"].toFloat(), posMap["y"].toFloat());
return PickParabola(pickRay.origin, getSpeed() * pickRay.direction, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,24 @@
//
// Created by Sam Gondelman 7/2/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_MouseParabolaPick_h
#define hifi_MouseParabolaPick_h
#include "ParabolaPick.h"
class MouseParabolaPick : public ParabolaPick {
public:
MouseParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar,
const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
bool isMouse() const override { return true; }
};
#endif // hifi_MouseParabolaPick_h

View file

@ -0,0 +1,70 @@
//
// Created by Sam Gondelman 7/2/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 "ParabolaPick.h"
#include "Application.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Overlays.h"
#include "avatar/AvatarManager.h"
#include "scripting/HMDScriptingInterface.h"
#include "DependencyManager.h"
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToEntityIntersectionResult entityRes =
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (entityRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToOverlayIntersectionResult overlayRes =
qApp->getOverlays().findParabolaIntersectionVector(pick, !getFilter().doesPickCoarse(),
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
if (overlayRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::OVERLAY, overlayRes.overlayID, overlayRes.distance, overlayRes.parabolicDistance, overlayRes.intersection, pick, overlayRes.surfaceNormal, overlayRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
ParabolaToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findParabolaIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
if (avatarRes.intersects) {
return std::make_shared<ParabolaPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.parabolicDistance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
}
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) {
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
float parabolicDistance;
glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateParabolaUICollisionPoint(pick.origin, pick.velocity, pick.acceleration, parabolicDistance);
return std::make_shared<ParabolaPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), parabolicDistance, hudRes, pick);
}
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
float ParabolaPick::getSpeed() const {
return (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() * _speed : _speed);
}
glm::vec3 ParabolaPick::getAcceleration() const {
float scale = (_scaleWithAvatar ? DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : 1.0f);
if (_rotateAccelerationWithAvatar) {
return scale * (DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * _accelerationAxis);
}
return scale * _accelerationAxis;
}

View file

@ -0,0 +1,97 @@
//
// Created by Sam Gondelman 7/2/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_ParabolaPick_h
#define hifi_ParabolaPick_h
#include <RegisteredMetaTypes.h>
#include <Pick.h>
class EntityItemID;
class OverlayID;
class ParabolaPickResult : public PickResult {
public:
ParabolaPickResult() {}
ParabolaPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
ParabolaPickResult(const IntersectionType type, const QUuid& objectID, float distance, float parabolicDistance, const glm::vec3& intersection, const PickParabola& parabola,
const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
PickResult(parabola.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), parabolicDistance(parabolicDistance), intersects(type != NONE) {
}
ParabolaPickResult(const ParabolaPickResult& parabolaPickResult) : PickResult(parabolaPickResult.pickVariant) {
type = parabolaPickResult.type;
intersects = parabolaPickResult.intersects;
objectID = parabolaPickResult.objectID;
distance = parabolaPickResult.distance;
parabolicDistance = parabolaPickResult.parabolicDistance;
intersection = parabolaPickResult.intersection;
surfaceNormal = parabolaPickResult.surfaceNormal;
extraInfo = parabolaPickResult.extraInfo;
}
QVariantMap extraInfo;
QUuid objectID;
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
IntersectionType type { NONE };
float distance { FLT_MAX };
float parabolicDistance { FLT_MAX };
bool intersects { false };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;
toReturn["type"] = type;
toReturn["intersects"] = intersects;
toReturn["objectID"] = objectID;
toReturn["distance"] = distance;
toReturn["parabolicDistance"] = parabolicDistance;
toReturn["intersection"] = vec3toVariant(intersection);
toReturn["surfaceNormal"] = vec3toVariant(surfaceNormal);
toReturn["parabola"] = PickResult::toVariantMap();
toReturn["extraInfo"] = extraInfo;
return toReturn;
}
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newParabolaRes = std::static_pointer_cast<ParabolaPickResult>(newRes);
if (newParabolaRes->parabolicDistance < parabolicDistance) {
return std::make_shared<ParabolaPickResult>(*newParabolaRes);
} else {
return std::make_shared<ParabolaPickResult>(*this);
}
}
};
class ParabolaPick : public Pick<PickParabola> {
public:
ParabolaPick(float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar, bool scaleWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateAccelerationWithAvatar(rotateAccelerationWithAvatar),
_scaleWithAvatar(scaleWithAvatar) {}
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override { return std::make_shared<ParabolaPickResult>(pickVariant); }
PickResultPointer getEntityIntersection(const PickParabola& pick) override;
PickResultPointer getOverlayIntersection(const PickParabola& pick) override;
PickResultPointer getAvatarIntersection(const PickParabola& pick) override;
PickResultPointer getHUDIntersection(const PickParabola& pick) override;
protected:
float _speed;
glm::vec3 _accelerationAxis;
bool _rotateAccelerationWithAvatar;
bool _scaleWithAvatar;
float getSpeed() const;
glm::vec3 getAcceleration() const;
};
#endif // hifi_ParabolaPick_h

View file

@ -0,0 +1,406 @@
//
// 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 "ParabolaPointer.h"
#include "Application.h"
#include "avatar/AvatarManager.h"
#include <StencilMaskPass.h>
#include <DependencyManager.h>
#include "ParabolaPick.h"
#include "render-utils/parabola_vert.h"
#include "render-utils/parabola_frag.h"
const glm::vec4 ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR { 1.0f };
const float ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH { 0.01f };
const bool ParabolaPointer::RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA { false };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_parabolaPipeline { nullptr };
gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::_transparentParabolaPipeline { nullptr };
ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover,
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd,
bool scaleWithAvatar, bool enabled) :
PathPointer(PickQuery::Parabola, rayProps, renderStates, defaultRenderStates, hover, triggers, faceAvatar, followNormal, followNormalStrength,
centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled)
{
}
void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) {
auto renderState = std::static_pointer_cast<RenderState>(_renderStates[state]);
if (renderState) {
QVariantMap pathMap = pathProps.toMap();
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
bool enabled = false;
if (!pathMap.isEmpty()) {
enabled = true;
if (pathMap["color"].isValid()) {
bool valid;
color = toGlm(xColorFromVariant(pathMap["color"], valid));
}
if (pathMap["alpha"].isValid()) {
alpha = pathMap["alpha"].toFloat();
}
if (pathMap["width"].isValid()) {
width = pathMap["width"].toFloat();
renderState->setPathWidth(width);
}
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
}
}
renderState->editParabola(color, alpha, width, isVisibleInSecondaryCamera, enabled);
}
}
glm::vec3 ParabolaPointer::getPickOrigin(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? vec3FromVariant(parabolaPickResult->pickVariant["origin"]) : glm::vec3(0.0f));
}
glm::vec3 ParabolaPointer::getPickEnd(const PickResultPointer& pickResult, float distance) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (distance > 0.0f) {
PickParabola pick = PickParabola(parabolaPickResult->pickVariant);
return pick.origin + pick.velocity * distance + 0.5f * pick.acceleration * distance * distance;
} else {
return parabolaPickResult->intersection;
}
}
glm::vec3 ParabolaPointer::getPickedObjectNormal(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->surfaceNormal : glm::vec3(0.0f));
}
IntersectionType ParabolaPointer::getPickedObjectType(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->type : IntersectionType::NONE);
}
QUuid ParabolaPointer::getPickedObjectID(const PickResultPointer& pickResult) const {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
return (parabolaPickResult ? parabolaPickResult->objectID : QUuid());
}
void ParabolaPointer::setVisualPickResultInternal(PickResultPointer pickResult, IntersectionType type, const QUuid& id,
const glm::vec3& intersection, float distance, const glm::vec3& surfaceNormal) {
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult) {
parabolaPickResult->type = type;
parabolaPickResult->objectID = id;
parabolaPickResult->intersection = intersection;
parabolaPickResult->distance = distance;
parabolaPickResult->surfaceNormal = surfaceNormal;
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
parabolaPickResult->pickVariant["velocity"] = vec3toVariant((intersection - parabola.origin -
0.5f * parabola.acceleration * parabolaPickResult->parabolicDistance * parabolaPickResult->parabolicDistance) / parabolaPickResult->parabolicDistance);
}
}
ParabolaPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
bool isVisibleInSecondaryCamera, bool pathEnabled) :
StartEndRenderState(startID, endID)
{
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
_pathID = scene->allocateID();
_pathWidth = pathWidth;
if (render::Item::isValidID(_pathID)) {
auto renderItem = std::make_shared<ParabolaRenderItem>(pathColor, pathAlpha, pathWidth, isVisibleInSecondaryCamera, pathEnabled);
// TODO: update bounds properly
renderItem->editBound().setBox(glm::vec3(-16000.0f), 32000.0f);
transaction.resetItem(_pathID, std::make_shared<ParabolaRenderItem::Payload>(renderItem));
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::cleanup() {
StartEndRenderState::cleanup();
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.removeItem(_pathID);
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::disable() {
StartEndRenderState::disable();
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.updateItem<ParabolaRenderItem>(_pathID, [](ParabolaRenderItem& item) {
item.setVisible(false);
});
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled) {
if (render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
transaction.updateItem<ParabolaRenderItem>(_pathID, [color, alpha, width, isVisibleInSecondaryCamera, enabled](ParabolaRenderItem& item) {
item.setColor(color);
item.setAlpha(alpha);
item.setWidth(width);
item.setIsVisibleInSecondaryCamera(isVisibleInSecondaryCamera);
item.setEnabled(enabled);
item.updateKey();
});
scene->enqueueTransaction(transaction);
}
}
void ParabolaPointer::RenderState::update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
StartEndRenderState::update(origin, end, surfaceNormal, scaleWithAvatar, distanceScaleEnd, centerEndY, faceAvatar, followNormal, followNormalStrength, distance, pickResult);
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult && render::Item::isValidID(_pathID)) {
render::Transaction transaction;
auto scene = qApp->getMain3DScene();
PickParabola parabola = PickParabola(parabolaPickResult->pickVariant);
glm::vec3 velocity = parabola.velocity;
glm::vec3 acceleration = parabola.acceleration;
float parabolicDistance = distance > 0.0f ? distance : parabolaPickResult->parabolicDistance;
float width = scaleWithAvatar ? getPathWidth() * DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale() : getPathWidth();
transaction.updateItem<ParabolaRenderItem>(_pathID, [origin, velocity, acceleration, parabolicDistance, width](ParabolaRenderItem& item) {
item.setVisible(true);
item.setOrigin(origin);
item.setVelocity(velocity);
item.setAcceleration(acceleration);
item.setParabolicDistance(parabolicDistance);
item.setWidth(width);
});
scene->enqueueTransaction(transaction);
}
}
std::shared_ptr<StartEndRenderState> ParabolaPointer::buildRenderState(const QVariantMap& propMap) {
QUuid startID;
if (propMap["start"].isValid()) {
QVariantMap startMap = propMap["start"].toMap();
if (startMap["type"].isValid()) {
startMap.remove("visible");
startID = qApp->getOverlays().addOverlay(startMap["type"].toString(), startMap);
}
}
glm::vec3 color = glm::vec3(RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR);
float alpha = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_COLOR.a;
float width = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_WIDTH;
bool isVisibleInSecondaryCamera = RenderState::ParabolaRenderItem::DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
bool enabled = false;
if (propMap["path"].isValid()) {
enabled = true;
QVariantMap pathMap = propMap["path"].toMap();
if (pathMap["color"].isValid()) {
bool valid;
color = toGlm(xColorFromVariant(pathMap["color"], valid));
}
if (pathMap["alpha"].isValid()) {
alpha = pathMap["alpha"].toFloat();
}
if (pathMap["width"].isValid()) {
width = pathMap["width"].toFloat();
}
if (pathMap["isVisibleInSecondaryCamera"].isValid()) {
isVisibleInSecondaryCamera = pathMap["isVisibleInSecondaryCamera"].toBool();
}
}
QUuid endID;
if (propMap["end"].isValid()) {
QVariantMap endMap = propMap["end"].toMap();
if (endMap["type"].isValid()) {
endMap.remove("visible");
endID = qApp->getOverlays().addOverlay(endMap["type"].toString(), endMap);
}
}
return std::make_shared<RenderState>(startID, endID, color, alpha, width, isVisibleInSecondaryCamera, enabled);
}
PointerEvent ParabolaPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button, bool hover) {
QUuid pickedID;
glm::vec3 intersection, surfaceNormal, origin, velocity, acceleration;
auto parabolaPickResult = std::static_pointer_cast<ParabolaPickResult>(pickResult);
if (parabolaPickResult) {
intersection = parabolaPickResult->intersection;
surfaceNormal = parabolaPickResult->surfaceNormal;
const QVariantMap& parabola = parabolaPickResult->pickVariant;
origin = vec3FromVariant(parabola["origin"]);
velocity = vec3FromVariant(parabola["velocity"]);
acceleration = vec3FromVariant(parabola["acceleration"]);
pickedID = parabolaPickResult->objectID;
}
if (pickedID != target.objectID) {
intersection = findIntersection(target, origin, velocity, acceleration);
}
glm::vec2 pos2D = findPos2D(target, intersection);
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
TriggerState& state = hover ? _latestState : _states[button];
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
pos2D = state.triggerPos2D;
intersection = state.intersection;
surfaceNormal = state.surfaceNormal;
}
if (!withinDeadspot) {
state.deadspotExpired = true;
}
return PointerEvent(pos2D, intersection, surfaceNormal, velocity);
}
glm::vec3 ParabolaPointer::findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration) {
// TODO: implement
switch (pickedObject.type) {
case ENTITY:
//return ParabolaPick::intersectParabolaWithEntityXYPlane(pickedObject.objectID, origin, velocity, acceleration);
case OVERLAY:
//return ParabolaPick::intersectParabolaWithOverlayXYPlane(pickedObject.objectID, origin, velocity, acceleration);
default:
return glm::vec3(NAN);
}
}
ParabolaPointer::RenderState::ParabolaRenderItem::ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
bool isVisibleInSecondaryCamera, bool enabled) :
_isVisibleInSecondaryCamera(isVisibleInSecondaryCamera), _enabled(enabled)
{
_uniformBuffer->resize(sizeof(ParabolaData));
setColor(color);
setAlpha(alpha);
setWidth(width);
updateKey();
}
void ParabolaPointer::RenderState::ParabolaRenderItem::setVisible(bool visible) {
if (visible && _enabled) {
_key = render::ItemKey::Builder(_key).withVisible();
} else {
_key = render::ItemKey::Builder(_key).withInvisible();
}
_visible = visible;
}
void ParabolaPointer::RenderState::ParabolaRenderItem::updateKey() {
// FIXME: There's no way to designate a render item as non-shadow-reciever, and since a parabola's bounding box covers the entire domain,
// it seems to block all shadows. I think this is a bug with shadows.
//auto builder = _parabolaData.color.a < 1.0f ? render::ItemKey::Builder::transparentShape() : render::ItemKey::Builder::opaqueShape();
auto builder = render::ItemKey::Builder::transparentShape();
if (_enabled && _visible) {
builder.withVisible();
} else {
builder.withInvisible();
}
if (_isVisibleInSecondaryCamera) {
builder.withTagBits(render::hifi::TAG_ALL_VIEWS);
} else {
builder.withTagBits(render::hifi::TAG_MAIN_VIEW);
}
_key = builder.build();
}
const gpu::PipelinePointer ParabolaPointer::RenderState::ParabolaRenderItem::getParabolaPipeline() {
if (!_parabolaPipeline || !_transparentParabolaPipeline) {
auto vs = parabola_vert::getShader();
auto ps = parabola_frag::getShader();
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
gpu::Shader::BindingSet slotBindings;
slotBindings.insert(gpu::Shader::Binding(std::string("parabolaData"), 0));
gpu::Shader::makeProgram(*program, slotBindings);
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(false,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMaskDrawShape(*state);
state->setCullMode(gpu::State::CULL_NONE);
_parabolaPipeline = gpu::Pipeline::create(program, state);
}
{
auto state = std::make_shared<gpu::State>();
state->setDepthTest(true, true, gpu::LESS_EQUAL);
state->setBlendFunction(true,
gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA,
gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE);
PrepareStencil::testMask(*state);
state->setCullMode(gpu::State::CULL_NONE);
_transparentParabolaPipeline = gpu::Pipeline::create(program, state);
}
}
return (_parabolaData.color.a < 1.0f ? _transparentParabolaPipeline : _parabolaPipeline);
}
void ParabolaPointer::RenderState::ParabolaRenderItem::render(RenderArgs* args) {
if (!_visible) {
return;
}
gpu::Batch& batch = *(args->_batch);
Transform transform;
transform.setTranslation(_origin);
batch.setModelTransform(transform);
batch.setPipeline(getParabolaPipeline());
const int MAX_SECTIONS = 100;
if (glm::length2(_parabolaData.acceleration) < EPSILON) {
_parabolaData.numSections = 1;
} else {
_parabolaData.numSections = glm::clamp((int)(_parabolaData.parabolicDistance + 1) * 10, 1, MAX_SECTIONS);
}
updateUniformBuffer();
batch.setUniformBuffer(0, _uniformBuffer);
// We draw 2 * n + 2 vertices for a triangle strip
batch.draw(gpu::TRIANGLE_STRIP, 2 * _parabolaData.numSections + 2, 0);
}
namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
return payload->getKey();
}
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
if (payload) {
return payload->getBound();
}
return Item::Bound();
}
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args) {
if (payload) {
payload->render(args);
}
}
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload) {
return ShapeKey::Builder::ownPipeline();
}
}

View file

@ -0,0 +1,125 @@
//
// 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_ParabolaPointer_h
#define hifi_ParabolaPointer_h
#include "PathPointer.h"
class ParabolaPointer : public PathPointer {
using Parent = PathPointer;
public:
class RenderState : public StartEndRenderState {
public:
class ParabolaRenderItem {
public:
using Payload = render::Payload<ParabolaRenderItem>;
using Pointer = Payload::DataPointer;
ParabolaRenderItem(const glm::vec3& color, float alpha, float width,
bool isVisibleInSecondaryCamera, bool enabled);
~ParabolaRenderItem() {}
static gpu::PipelinePointer _parabolaPipeline;
static gpu::PipelinePointer _transparentParabolaPipeline;
const gpu::PipelinePointer getParabolaPipeline();
void render(RenderArgs* args);
render::Item::Bound& editBound() { return _bound; }
const render::Item::Bound& getBound() { return _bound; }
render::ItemKey getKey() const { return _key; }
void setVisible(bool visible);
void updateKey();
void updateUniformBuffer() { _uniformBuffer->setSubData(0, _parabolaData); }
void setColor(const glm::vec3& color) { _parabolaData.color = glm::vec4(color, _parabolaData.color.a); }
void setAlpha(const float& alpha) { _parabolaData.color.a = alpha; }
void setWidth(const float& width) { _parabolaData.width = width; }
void setParabolicDistance(const float& parabolicDistance) { _parabolaData.parabolicDistance = parabolicDistance; }
void setVelocity(const glm::vec3& velocity) { _parabolaData.velocity = velocity; }
void setAcceleration(const glm::vec3& acceleration) { _parabolaData.acceleration = acceleration; }
void setOrigin(const glm::vec3& origin) { _origin = origin; }
void setIsVisibleInSecondaryCamera(const bool& isVisibleInSecondaryCamera) { _isVisibleInSecondaryCamera = isVisibleInSecondaryCamera; }
void setEnabled(const bool& enabled) { _enabled = enabled; }
static const glm::vec4 DEFAULT_PARABOLA_COLOR;
static const float DEFAULT_PARABOLA_WIDTH;
static const bool DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA;
private:
render::Item::Bound _bound;
render::ItemKey _key;
glm::vec3 _origin { 0.0f };
bool _isVisibleInSecondaryCamera { DEFAULT_PARABOLA_ISVISIBLEINSECONDARYCAMERA };
bool _visible { false };
bool _enabled { false };
struct ParabolaData {
glm::vec3 velocity { 0.0f };
float parabolicDistance { 0.0f };
vec3 acceleration { 0.0f };
float width { DEFAULT_PARABOLA_WIDTH };
vec4 color { vec4(DEFAULT_PARABOLA_COLOR)};
int numSections { 0 };
ivec3 spare;
};
ParabolaData _parabolaData;
gpu::BufferPointer _uniformBuffer { std::make_shared<gpu::Buffer>() };
};
RenderState() {}
RenderState(const OverlayID& startID, const OverlayID& endID, const glm::vec3& pathColor, float pathAlpha, float pathWidth,
bool isVisibleInSecondaryCamera, bool pathEnabled);
void setPathWidth(float width) { _pathWidth = width; }
float getPathWidth() const { return _pathWidth; }
void cleanup() override;
void disable() override;
void update(const glm::vec3& origin, const glm::vec3& end, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) override;
void editParabola(const glm::vec3& color, float alpha, float width, bool isVisibleInSecondaryCamera, bool enabled);
private:
int _pathID;
float _pathWidth;
};
ParabolaPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers,
bool faceAvatar, bool followNormal, float followNormalStrength, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
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) const override;
glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance) const override;
glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const override;
IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const override;
QUuid getPickedObjectID(const PickResultPointer& pickResult) const 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;
private:
static glm::vec3 findIntersection(const PickedObject& pickedObject, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration);
};
namespace render {
template <> const ItemKey payloadGetKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> const Item::Bound payloadGetBound(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
template <> void payloadRender(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload, RenderArgs* args);
template <> const ShapeKey shapeGetShapeKey(const ParabolaPointer::RenderState::ParabolaRenderItem::Pointer& payload);
}
#endif // hifi_ParabolaPointer_h

View file

@ -0,0 +1,353 @@
//
// 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, float followNormalStrength, 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),
_followNormalStrength(followNormalStrength),
_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;
distance = glm::distance(origin, endVec);
glm::vec3 normalizedDirection = glm::normalize(direction);
type = _lockEndObject.isOverlay ? IntersectionType::OVERLAY : IntersectionType::ENTITY;
id = _lockEndObject.id;
intersection = endVec;
surfaceNormal = -normalizedDirection;
setVisualPickResultInternal(visualPickResult, type, id, intersection, distance, surfaceNormal);
} else if (type != IntersectionType::NONE && _lockEnd) {
id = getPickedObjectID(pickResult);
if (type == IntersectionType::ENTITY) {
endVec = DependencyManager::get<EntityScriptingInterface>()->getEntityTransform(id)[3];
} else if (type == IntersectionType::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);
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)) {
glm::vec3 origin = getPickOrigin(pickResult);
glm::vec3 end = getPickEnd(pickResult, _pathLength);
glm::vec3 surfaceNormal = getPickedObjectNormal(pickResult);
_renderStates[_currentRenderState]->update(origin, end, surfaceNormal, _scaleWithAvatar, _distanceScaleEnd, _centerEndY, _faceAvatar,
_followNormal, _followNormalStrength, _pathLength, pickResult);
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, Vectors::UP, _scaleWithAvatar, _distanceScaleEnd, _centerEndY,
_faceAvatar, _followNormal, _followNormalStrength, _defaultRenderStates[_currentRenderState].first, pickResult);
} 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 = endProps.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, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult) {
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::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));
}
glm::quat normalQuat = Quat().lookAtSimple(Vectors::ZERO, surfaceNormal);
normalQuat = normalQuat * glm::quat(glm::vec3(-M_PI_2, 0, 0));
glm::vec3 avatarUp = DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
glm::quat rotation = glm::rotation(Vectors::UP, avatarUp);
glm::vec3 position = end;
if (!centerEndY) {
if (followNormal) {
position = end + 0.5f * dim.y * surfaceNormal;
} else {
position = end + 0.5f * dim.y * avatarUp;
}
}
if (faceAvatar) {
glm::quat orientation = followNormal ? normalQuat : DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation();
glm::quat lookAtWorld = Quat().lookAt(position, DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldPosition(), surfaceNormal);
glm::quat lookAtModel = glm::inverse(orientation) * lookAtWorld;
glm::quat lookAtFlatModel = Quat().cancelOutRollAndPitch(lookAtModel);
glm::quat lookAtFlatWorld = orientation * lookAtFlatModel;
rotation = lookAtFlatWorld;
} else if (followNormal) {
rotation = normalQuat;
}
if (followNormal && followNormalStrength > 0.0f && followNormalStrength < 1.0f) {
if (!_avgEndRotInitialized) {
_avgEndRot = rotation;
_avgEndRotInitialized = true;
} else {
rotation = glm::slerp(_avgEndRot, rotation, followNormalStrength);
if (!centerEndY) {
position = end + 0.5f * dim.y * (rotation * Vectors::UP);
}
_avgEndRot = rotation;
}
}
endProps.insert("position", vec3toVariant(position));
endProps.insert("rotation", quatToVariant(rotation));
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,135 @@
//
// 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, const glm::vec3& surfaceNormal, bool scaleWithAvatar, bool distanceScaleEnd, bool centerEndY,
bool faceAvatar, bool followNormal, float followNormalStrength, float distance, const PickResultPointer& pickResult);
protected:
OverlayID _startID;
OverlayID _endID;
bool _startIgnoreRays;
bool _endIgnoreRays;
glm::vec3 _startDim;
glm::vec3 _endDim;
glm::quat _endRot;
glm::quat _avgEndRot;
bool _avgEndRotInitialized { false };
};
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, float followNormalStrength, 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 type of any part of the 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:
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
std::string _currentRenderState { "" };
PointerTriggers _triggers;
float _pathLength { 0.0f };
bool _faceAvatar;
bool _followNormal;
float _followNormalStrength;
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) const = 0;
virtual glm::vec3 getPickEnd(const PickResultPointer& pickResult, float distance = 0.0f) const = 0;
virtual glm::vec3 getPickedObjectNormal(const PickResultPointer& pickResult) const = 0;
virtual IntersectionType getPickedObjectType(const PickResultPointer& pickResult) const = 0;
virtual QUuid getPickedObjectID(const PickResultPointer& pickResult) const = 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

@ -17,6 +17,9 @@
#include "JointRayPick.h"
#include "MouseRayPick.h"
#include "StylusPick.h"
#include "StaticParabolaPick.h"
#include "JointParabolaPick.h"
#include "MouseParabolaPick.h"
#include <ScriptEngine.h>
@ -26,6 +29,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
return createRayPick(properties);
case PickQuery::PickType::Stylus:
return createStylusPick(properties);
case PickQuery::PickType::Parabola:
return createParabolaPick(properties);
default:
return PickManager::INVALID_PICK_ID;
}
@ -134,6 +139,101 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
return DependencyManager::get<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(side, filter, maxDistance, enabled));
}
/**jsdoc
* A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick.
* @typedef {object} Picks.ParabolaPickProperties
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
* @property {string} [joint] Only for Joint or Mouse Parabola Picks. If "Mouse", it will create a Parabola Pick that follows the system mouse, in desktop or HMD.
* If "Avatar", it will create a Joint Parabola Pick that follows your avatar's head. Otherwise, it will create a Joint Parabola Pick that follows the given joint, if it
* exists on your current avatar.
* @property {Vec3} [posOffset=Vec3.ZERO] Only for Joint Parabola Picks. A local joint position offset, in meters. x = upward, y = forward, z = lateral
* @property {Vec3} [dirOffset=Vec3.UP] Only for Joint Parabola Picks. A local joint direction offset. x = upward, y = forward, z = lateral
* @property {Vec3} [position] Only for Static Parabola Picks. The world-space origin of the parabola segment.
* @property {Vec3} [direction=-Vec3.FRONT] Only for Static Parabola Picks. The world-space direction of the parabola segment.
* @property {number} [speed=1] The initial speed of the parabola, i.e. the initial speed of the projectile whose trajectory defines the parabola.
* @property {Vec3} [accelerationAxis=-Vec3.UP] The acceleration of the parabola, i.e. the acceleration of the projectile whose trajectory defines the parabola, both magnitude and direction.
* @property {boolean} [rotateAccelerationWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis.
* @property {boolean} [scaleWithAvatar=false] If true, the velocity and acceleration of the Pick will scale linearly with your avatar.
*/
unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properties) {
QVariantMap propMap = properties.toMap();
bool enabled = false;
if (propMap["enabled"].isValid()) {
enabled = propMap["enabled"].toBool();
}
PickFilter filter = PickFilter();
if (propMap["filter"].isValid()) {
filter = PickFilter(propMap["filter"].toUInt());
}
float maxDistance = 0.0f;
if (propMap["maxDistance"].isValid()) {
maxDistance = propMap["maxDistance"].toFloat();
}
float speed = 1.0f;
if (propMap["speed"].isValid()) {
speed = propMap["speed"].toFloat();
}
glm::vec3 accelerationAxis = -Vectors::UP;
if (propMap["accelerationAxis"].isValid()) {
accelerationAxis = vec3FromVariant(propMap["accelerationAxis"]);
}
bool rotateAccelerationWithAvatar = true;
if (propMap["rotateAccelerationWithAvatar"].isValid()) {
rotateAccelerationWithAvatar = propMap["rotateAccelerationWithAvatar"].toBool();
}
bool scaleWithAvatar = false;
if (propMap["scaleWithAvatar"].isValid()) {
scaleWithAvatar = propMap["scaleWithAvatar"].toBool();
}
if (propMap["joint"].isValid()) {
std::string jointName = propMap["joint"].toString().toStdString();
if (jointName != "Mouse") {
// x = upward, y = forward, z = lateral
glm::vec3 posOffset = Vectors::ZERO;
if (propMap["posOffset"].isValid()) {
posOffset = vec3FromVariant(propMap["posOffset"]);
}
glm::vec3 dirOffset = Vectors::UP;
if (propMap["dirOffset"].isValid()) {
dirOffset = vec3FromVariant(propMap["dirOffset"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<JointParabolaPick>(jointName, posOffset, dirOffset,
speed, accelerationAxis, rotateAccelerationWithAvatar,
scaleWithAvatar, filter, maxDistance, enabled));
} else {
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<MouseParabolaPick>(speed, accelerationAxis, rotateAccelerationWithAvatar,
scaleWithAvatar, filter, maxDistance, enabled));
}
} else if (propMap["position"].isValid()) {
glm::vec3 position = vec3FromVariant(propMap["position"]);
glm::vec3 direction = -Vectors::FRONT;
if (propMap["direction"].isValid()) {
direction = vec3FromVariant(propMap["direction"]);
}
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<StaticParabolaPick>(position, direction, speed, accelerationAxis,
rotateAccelerationWithAvatar, scaleWithAvatar,
filter, maxDistance, enabled));
}
return PickManager::INVALID_PICK_ID;
}
void PickScriptingInterface::enablePick(unsigned int uid) {
DependencyManager::get<PickManager>()->enablePick(uid);
}

View file

@ -62,6 +62,7 @@ class PickScriptingInterface : public QObject, public Dependency {
public:
unsigned int createRayPick(const QVariant& properties);
unsigned int createStylusPick(const QVariant& properties);
unsigned int createParabolaPick(const QVariant& properties);
void registerMetaTypes(QScriptEngine* engine);
@ -71,7 +72,7 @@ public:
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
* @function Picks.createPick
* @param {PickType} type A PickType that specifies the method of picking to use
* @param {Picks.RayPickProperties|Picks.StylusPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
*/
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
@ -125,6 +126,21 @@ public:
* @property {StylusTip} stylusTip The StylusTip that was used. Valid even if there was no intersection.
*/
/**jsdoc
* An intersection result for a Parabola Pick.
*
* @typedef {object} ParabolaPickResult
* @property {number} type The intersection type.
* @property {boolean} intersects If there was a valid intersection (type != INTERSECTED_NONE)
* @property {Uuid} objectID The ID of the intersected object. Uuid.NULL for the HUD or invalid intersections.
* @property {number} distance The distance to the intersection point from the origin of the parabola, not along the parabola.
* @property {number} parabolicDistance The distance to the intersection point from the origin of the parabola, along the parabola.
* @property {Vec3} intersection The intersection point in world-space.
* @property {Vec3} surfaceNormal The surface normal at the intersected point. All NANs if type == INTERSECTED_HUD.
* @property {Variant} extraInfo Additional intersection details when available for Model objects.
* @property {StylusTip} parabola The PickParabola that was used. Valid even if there was no intersection.
*/
/**jsdoc
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
* @function Picks.getPrevPickResult
@ -162,7 +178,7 @@ public:
* Check if a Pick is associated with the left hand.
* @function Picks.isLeftHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_LEFTHAND" or "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", or a Stylus Pick with hand == 0.
*/
Q_INVOKABLE bool isLeftHand(unsigned int uid);
@ -170,7 +186,7 @@ public:
* Check if a Pick is associated with the right hand.
* @function Picks.isRightHand
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Joint Ray Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
* @returns {boolean} True if the Pick is a Joint Ray or Parabola Pick with joint == "_CONTROLLER_RIGHTHAND" or "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", or a Stylus Pick with hand == 1.
*/
Q_INVOKABLE bool isRightHand(unsigned int uid);
@ -178,7 +194,7 @@ public:
* Check if a Pick is associated with the system mouse.
* @function Picks.isMouse
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
* @returns {boolean} True if the Pick is a Mouse Ray Pick, false otherwise.
* @returns {boolean} True if the Pick is a Mouse Ray or Parabola Pick, false otherwise.
*/
Q_INVOKABLE bool isMouse(unsigned int uid);

View file

@ -15,6 +15,7 @@
#include "Application.h"
#include "LaserPointer.h"
#include "StylusPointer.h"
#include "ParabolaPointer.h"
void PointerScriptingInterface::setIgnoreItems(unsigned int uid, const QScriptValue& ignoreItems) const {
DependencyManager::get<PointerManager>()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems));
@ -37,6 +38,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 +87,22 @@ 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 {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
* the normal will follow exactly.
* @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();
@ -134,12 +132,21 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool followNormal = false;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
float followNormalStrength = 0.0f;
if (propertyMap["followNormalStrength"].isValid()) {
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
LaserPointer::RenderStateMap renderStates;
RenderStateMap renderStates;
if (propertyMap["renderStates"].isValid()) {
QList<QVariant> renderStateVariants = propertyMap["renderStates"].toList();
for (const QVariant& renderStateVariant : renderStateVariants) {
@ -153,7 +160,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) {
@ -162,7 +169,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));
}
}
}
@ -192,7 +199,151 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
}
return DependencyManager::get<PointerManager>()->addPointer(std::make_shared<LaserPointer>(properties, renderStates, defaultRenderStates, hover, triggers,
faceAvatar, centerEndY, lockEnd, distanceScaleEnd, scaleWithAvatar, enabled));
faceAvatar, followNormal, followNormalStrength, centerEndY, lockEnd,
distanceScaleEnd, 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 {number} [followNormalStrength=0.0] The strength of the interpolation between the real normal and the visual normal if followNormal is true. <code>0-1</code>. If 0 or 1,
* the normal will follow exactly.
* @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 {
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 = false;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
float followNormalStrength = 0.0f;
if (propertyMap["followNormalStrength"].isValid()) {
followNormalStrength = propertyMap["followNormalStrength"].toFloat();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
}
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);
}
}
}
}
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, std::shared_ptr<StartEndRenderState>>(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, followNormalStrength, centerEndY, lockEnd, distanceScaleEnd,
scaleWithAvatar, enabled));
}
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {

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.
*

View file

@ -39,7 +39,7 @@ PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
PickResultPointer RayPick::getAvatarIntersection(const PickRay& pick) {
RayToAvatarIntersectionResult avatarRes = DependencyManager::get<AvatarManager>()->findRayIntersectionVector(pick, getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
if (avatarRes.intersects) {
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, glm::vec3(NAN), avatarRes.extraInfo);
return std::make_shared<RayPickResult>(IntersectionType::AVATAR, avatarRes.avatarID, avatarRes.distance, avatarRes.intersection, pick, avatarRes.surfaceNormal, avatarRes.extraInfo);
} else {
return std::make_shared<RayPickResult>(pick.toVariantMap());
}

View file

@ -19,7 +19,7 @@ public:
RayPickResult() {}
RayPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
RayPickResult(const IntersectionType type, const QUuid& objectID, float distance, const glm::vec3& intersection, const PickRay& searchRay, const glm::vec3& surfaceNormal = glm::vec3(NAN), const QVariantMap& extraInfo = QVariantMap()) :
PickResult(searchRay.toVariantMap()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
PickResult(searchRay.toVariantMap()), extraInfo(extraInfo), objectID(objectID), intersection(intersection), surfaceNormal(surfaceNormal), type(type), distance(distance), intersects(type != NONE) {
}
RayPickResult(const RayPickResult& rayPickResult) : PickResult(rayPickResult.pickVariant) {
@ -32,13 +32,13 @@ public:
extraInfo = rayPickResult.extraInfo;
}
IntersectionType type { NONE };
bool intersects { false };
QVariantMap extraInfo;
QUuid objectID;
float distance { FLT_MAX };
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
QVariantMap extraInfo;
IntersectionType type { NONE };
float distance { FLT_MAX };
bool intersects { false };
virtual QVariantMap toVariantMap() const override {
QVariantMap toReturn;

View file

@ -0,0 +1,19 @@
//
// Created by Sam Gondelman 7/2/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 "StaticParabolaPick.h"
StaticParabolaPick::StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis,
bool scaleWithAvatar, bool rotateAccelerationWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateAccelerationWithAvatar, scaleWithAvatar, filter, maxDistance, enabled),
_position(position), _velocity(direction)
{
}
PickParabola StaticParabolaPick::getMathematicalPick() const {
return PickParabola(_position, getSpeed() * _velocity, getAcceleration());
}

View file

@ -0,0 +1,26 @@
//
// Created by Sam Gondelman 7/2/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_StaticParabolaPick_h
#define hifi_StaticParabolaPick_h
#include "ParabolaPick.h"
class StaticParabolaPick : public ParabolaPick {
public:
StaticParabolaPick(const glm::vec3& position, const glm::vec3& direction, float speed, const glm::vec3& accelerationAxis, bool rotateAccelerationWithAvatar,
bool scaleWithAvatar, const PickFilter& filter, float maxDistance = 0.0f, bool enabled = false);
PickParabola getMathematicalPick() const override;
private:
glm::vec3 _position;
glm::vec3 _velocity;
};
#endif // hifi_StaticParabolaPick_h

View file

@ -35,6 +35,12 @@ glm::vec3 HMDScriptingInterface::calculateRayUICollisionPoint(const glm::vec3& p
return result;
}
glm::vec3 HMDScriptingInterface::calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const {
glm::vec3 result;
qApp->getApplicationCompositor().calculateParabolaUICollisionPoint(position, velocity, acceleration, result, parabolicDistance);
return result;
}
glm::vec2 HMDScriptingInterface::overlayFromWorldPoint(const glm::vec3& position) const {
return qApp->getApplicationCompositor().overlayFromSphereSurface(position);
}

View file

@ -100,6 +100,8 @@ public:
*/
Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const;
glm::vec3 calculateParabolaUICollisionPoint(const glm::vec3& position, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance) const;
/**jsdoc
* Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay.
* 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay.

View file

@ -23,7 +23,7 @@ Base3DOverlay::Base3DOverlay() :
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
_isSolid(DEFAULT_IS_SOLID),
_isDashedLine(DEFAULT_IS_DASHED_LINE),
_ignoreRayIntersection(false),
_ignorePickIntersection(false),
_drawInFront(false),
_drawHUDLayer(false)
{
@ -34,7 +34,7 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) :
SpatiallyNestable(NestableType::Overlay, QUuid::createUuid()),
_isSolid(base3DOverlay->_isSolid),
_isDashedLine(base3DOverlay->_isDashedLine),
_ignoreRayIntersection(base3DOverlay->_ignoreRayIntersection),
_ignorePickIntersection(base3DOverlay->_ignorePickIntersection),
_drawInFront(base3DOverlay->_drawInFront),
_drawHUDLayer(base3DOverlay->_drawHUDLayer),
_isGrabbable(base3DOverlay->_isGrabbable),
@ -183,8 +183,10 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
if (properties["dashed"].isValid()) {
setIsDashedLine(properties["dashed"].toBool());
}
if (properties["ignoreRayIntersection"].isValid()) {
setIgnoreRayIntersection(properties["ignoreRayIntersection"].toBool());
if (properties["ignorePickIntersection"].isValid()) {
setIgnorePickIntersection(properties["ignorePickIntersection"].toBool());
} else if (properties["ignoreRayIntersection"].isValid()) {
setIgnorePickIntersection(properties["ignoreRayIntersection"].toBool());
}
if (properties["parentID"].isValid()) {
@ -224,8 +226,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
@ -260,8 +261,8 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
if (property == "isDashedLine" || property == "dashed") {
return _isDashedLine;
}
if (property == "ignoreRayIntersection") {
return _ignoreRayIntersection;
if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") {
return _ignorePickIntersection;
}
if (property == "drawInFront") {
return _drawInFront;
@ -282,11 +283,6 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
return Overlay::getProperty(property);
}
bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
return false;
}
void Base3DOverlay::locationChanged(bool tellPhysics) {
SpatiallyNestable::locationChanged(tellPhysics);

View file

@ -45,7 +45,7 @@ public:
bool getIsSolid() const { return _isSolid; }
bool getIsDashedLine() const { return _isDashedLine; }
bool getIsSolidLine() const { return !_isDashedLine; }
bool getIgnoreRayIntersection() const { return _ignoreRayIntersection; }
bool getIgnorePickIntersection() const { return _ignorePickIntersection; }
bool getDrawInFront() const { return _drawInFront; }
bool getDrawHUDLayer() const { return _drawHUDLayer; }
bool getIsGrabbable() const { return _isGrabbable; }
@ -53,7 +53,7 @@ public:
void setIsSolid(bool isSolid) { _isSolid = isSolid; }
void setIsDashedLine(bool isDashedLine) { _isDashedLine = isDashedLine; }
void setIgnoreRayIntersection(bool value) { _ignoreRayIntersection = value; }
void setIgnorePickIntersection(bool value) { _ignorePickIntersection = value; }
virtual void setDrawInFront(bool value) { _drawInFront = value; }
virtual void setDrawHUDLayer(bool value) { _drawHUDLayer = value; }
void setIsGrabbable(bool value) { _isGrabbable = value; }
@ -69,13 +69,21 @@ public:
virtual QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false);
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; }
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) {
return findRayIntersection(origin, direction, distance, face, surfaceNormal, precisionPicking);
}
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) { return false; }
virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) {
return findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, precisionPicking);
}
virtual SpatialParentTree* getParentTree() const override;
protected:
@ -91,7 +99,7 @@ protected:
bool _isSolid;
bool _isDashedLine;
bool _ignoreRayIntersection;
bool _ignorePickIntersection;
bool _drawInFront;
bool _drawHUDLayer;
bool _isGrabbable { false };

View file

@ -397,8 +397,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
@ -520,22 +519,66 @@ QVariant Circle3DOverlay::getProperty(const QString& property) {
bool Circle3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
// Scale the dimensions by the diameter
glm::vec2 dimensions = getOuterRadius() * 2.0f * getDimensions();
bool intersects = findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), dimensions, distance);
glm::quat rotation = getWorldOrientation();
if (intersects) {
if (findRayRectangleIntersection(origin, direction, rotation, getWorldPosition(), dimensions, distance)) {
glm::vec3 hitPosition = origin + (distance * direction);
glm::vec3 localHitPosition = glm::inverse(getWorldOrientation()) * (hitPosition - getWorldPosition());
localHitPosition.x /= getDimensions().x;
localHitPosition.y /= getDimensions().y;
float distanceToHit = glm::length(localHitPosition);
intersects = getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius();
if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) {
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
}
}
return intersects;
return false;
}
bool Circle3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
// Scale the dimensions by the diameter
glm::vec2 xyDimensions = getOuterRadius() * 2.0f * getDimensions();
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition();
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
glm::vec3 localHitPosition = localOrigin + localVelocity * parabolicDistance + 0.5f * localAcceleration * parabolicDistance * parabolicDistance;
localHitPosition.x /= getDimensions().x;
localHitPosition.y /= getDimensions().y;
float distanceToHit = glm::length(localHitPosition);
if (getInnerRadius() <= distanceToHit && distanceToHit <= getOuterRadius()) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
}
}
return false;
}
Circle3DOverlay* Circle3DOverlay::createClone() const {

View file

@ -54,8 +54,10 @@ public:
void setMajorTickMarksColor(const xColor& value) { _majorTickMarksColor = value; }
void setMinorTickMarksColor(const xColor& value) { _minorTickMarksColor = value; }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual Circle3DOverlay* createClone() const override;

View file

@ -197,7 +197,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID&
_contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN);
_contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX);
_contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE);
_contextOverlay->setIgnoreRayIntersection(false);
_contextOverlay->setIgnorePickIntersection(false);
_contextOverlay->setDrawInFront(true);
_contextOverlay->setURL(PathUtils::resourcesUrl() + "images/inspect-icon.png");
_contextOverlay->setIsFacingAvatar(true);

View file

@ -160,8 +160,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -145,8 +145,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -35,7 +35,10 @@ public:
virtual Grid3DOverlay* createClone() const override;
// Grids are UI tools, and may not be intersected (pickable)
virtual bool findRayIntersection(const glm::vec3&, const glm::vec3&, float&, BoxFace&, glm::vec3&, bool precisionPicking = false) override { return false; }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face,
glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; }
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override { return false; }
protected:
Transform evalRenderTransform() override;

View file

@ -216,8 +216,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
@ -260,10 +259,7 @@ void Image3DOverlay::setURL(const QString& url) {
bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
if (_texture && _texture->isLoaded()) {
// Make sure position and rotation is updated.
Transform transform = getTransform();
// Don't call applyTransformTo() or setTransform() here because this code runs too frequently.
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
bool isNull = _fromImage.isNull();
@ -271,12 +267,55 @@ bool Image3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec
float height = isNull ? _texture->getHeight() : _fromImage.height();
float maxSize = glm::max(width, height);
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
glm::quat rotation = transform.getRotation();
// FIXME - face and surfaceNormal not being set
return findRayRectangleIntersection(origin, direction,
transform.getRotation(),
transform.getTranslation(),
dimensions, distance);
if (findRayRectangleIntersection(origin, direction, rotation, transform.getTranslation(), dimensions, distance)) {
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
}
}
return false;
}
bool Image3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
if (_texture && _texture->isLoaded()) {
Transform transform = getTransform();
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
bool isNull = _fromImage.isNull();
float width = isNull ? _texture->getWidth() : _fromImage.width();
float height = isNull ? _texture->getHeight() : _fromImage.height();
float maxSize = glm::max(width, height);
glm::vec2 dimensions = _dimensions * glm::vec2(width / maxSize, height / maxSize);
glm::quat rotation = transform.getRotation();
glm::vec3 position = getWorldPosition();
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, dimensions, parabolicDistance)) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
}
}
return false;

View file

@ -42,8 +42,10 @@ public:
QVariant getProperty(const QString& property) override;
bool isTransparent() override { return Base3DOverlay::isTransparent() || _alphaTexture; }
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual Image3DOverlay* createClone() const override;

View file

@ -288,8 +288,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -381,8 +381,7 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} isGroupCulled=false - If <code>true</code>, the mesh parts of the model are LOD culled as a group.
@ -518,17 +517,26 @@ QVariant ModelOverlay::getProperty(const QString& property) {
bool ModelOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
QVariantMap extraInfo;
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
}
bool ModelOverlay::findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) {
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, face, surfaceNormal, extraInfo, precisionPicking);
}
bool ModelOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
QVariantMap extraInfo;
return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking);
}
bool ModelOverlay::findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) {
return _model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance, face, surfaceNormal, extraInfo, precisionPicking);
}
ModelOverlay* ModelOverlay::createClone() const {
return new ModelOverlay(this);
}

View file

@ -47,7 +47,11 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findRayIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findParabolaIntersectionExtraInfo(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking = false) override;
virtual ModelOverlay* createClone() const override;

View file

@ -546,7 +546,7 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
continue;
}
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnoreRayIntersection() && thisOverlay->isLoaded()) {
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) {
float thisDistance;
BoxFace thisFace;
glm::vec3 thisSurfaceNormal;
@ -573,76 +573,86 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
return result;
}
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
auto obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("overlayID", OverlayIDtoScriptValue(engine, value.overlayID));
obj.setProperty("distance", value.distance);
ParabolaToOverlayIntersectionResult Overlays::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly, bool collidableOnly) {
float bestDistance = std::numeric_limits<float>::max();
bool bestIsFront = false;
QString faceName = "";
// handle BoxFace
switch (value.face) {
case MIN_X_FACE:
faceName = "MIN_X_FACE";
break;
case MAX_X_FACE:
faceName = "MAX_X_FACE";
break;
case MIN_Y_FACE:
faceName = "MIN_Y_FACE";
break;
case MAX_Y_FACE:
faceName = "MAX_Y_FACE";
break;
case MIN_Z_FACE:
faceName = "MIN_Z_FACE";
break;
case MAX_Z_FACE:
faceName = "MAX_Z_FACE";
break;
default:
case UNKNOWN_FACE:
faceName = "UNKNOWN_FACE";
break;
QMutexLocker locker(&_mutex);
ParabolaToOverlayIntersectionResult result;
QMapIterator<OverlayID, Overlay::Pointer> i(_overlaysWorld);
while (i.hasNext()) {
i.next();
OverlayID thisID = i.key();
auto thisOverlay = std::dynamic_pointer_cast<Base3DOverlay>(i.value());
if ((overlaysToDiscard.size() > 0 && overlaysToDiscard.contains(thisID)) ||
(overlaysToInclude.size() > 0 && !overlaysToInclude.contains(thisID))) {
continue;
}
if (thisOverlay && thisOverlay->getVisible() && !thisOverlay->getIgnorePickIntersection() && thisOverlay->isLoaded()) {
float thisDistance;
BoxFace thisFace;
glm::vec3 thisSurfaceNormal;
QVariantMap thisExtraInfo;
if (thisOverlay->findParabolaIntersectionExtraInfo(parabola.origin, parabola.velocity, parabola.acceleration, thisDistance,
thisFace, thisSurfaceNormal, thisExtraInfo, precisionPicking)) {
bool isDrawInFront = thisOverlay->getDrawInFront();
if ((bestIsFront && isDrawInFront && thisDistance < bestDistance)
|| (!bestIsFront && (isDrawInFront || thisDistance < bestDistance))) {
bestIsFront = isDrawInFront;
bestDistance = thisDistance;
result.intersects = true;
result.parabolicDistance = thisDistance;
result.face = thisFace;
result.surfaceNormal = thisSurfaceNormal;
result.overlayID = thisID;
result.intersection = parabola.origin + parabola.velocity * thisDistance + 0.5f * parabola.acceleration * thisDistance * thisDistance;
result.distance = glm::distance(result.intersection, parabola.origin);
result.extraInfo = thisExtraInfo;
}
}
}
}
obj.setProperty("face", faceName);
auto intersection = vec3toScriptValue(engine, value.intersection);
return result;
}
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
QScriptValue overlayIDValue = quuidToScriptValue(engine, value.overlayID);
obj.setProperty("overlayID", overlayIDValue);
obj.setProperty("distance", value.distance);
obj.setProperty("face", boxFaceToString(value.face));
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
return obj;
}
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& objectVar, RayToOverlayIntersectionResult& value) {
QVariantMap object = objectVar.toVariant().toMap();
value.intersects = object["intersects"].toBool();
value.overlayID = OverlayID(QUuid(object["overlayID"].toString()));
value.distance = object["distance"].toFloat();
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) {
value.intersects = object.property("intersects").toVariant().toBool();
QScriptValue overlayIDValue = object.property("overlayID");
quuidFromScriptValue(overlayIDValue, value.overlayID);
value.distance = object.property("distance").toVariant().toFloat();
value.face = boxFaceFromString(object.property("face").toVariant().toString());
QString faceName = object["face"].toString();
if (faceName == "MIN_X_FACE") {
value.face = MIN_X_FACE;
} else if (faceName == "MAX_X_FACE") {
value.face = MAX_X_FACE;
} else if (faceName == "MIN_Y_FACE") {
value.face = MIN_Y_FACE;
} else if (faceName == "MAX_Y_FACE") {
value.face = MAX_Y_FACE;
} else if (faceName == "MIN_Z_FACE") {
value.face = MIN_Z_FACE;
} else if (faceName == "MAX_Z_FACE") {
value.face = MAX_Z_FACE;
} else {
value.face = UNKNOWN_FACE;
};
auto intersection = object["intersection"];
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
bool valid;
auto newIntersection = vec3FromVariant(intersection, valid);
if (valid) {
value.intersection = newIntersection;
}
vec3FromScriptValue(intersection, value.intersection);
}
value.extraInfo = object["extraInfo"].toMap();
QScriptValue surfaceNormal = object.property("surfaceNormal");
if (surfaceNormal.isValid()) {
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
}
value.extraInfo = object.property("extraInfo").toVariant().toMap();
}
bool Overlays::isLoaded(OverlayID id) {
@ -1046,7 +1056,8 @@ QVector<QUuid> Overlays::findOverlays(const glm::vec3& center, float radius) {
i.next();
OverlayID thisID = i.key();
auto overlay = std::dynamic_pointer_cast<Volume3DOverlay>(i.value());
if (overlay && overlay->getVisible() && !overlay->getIgnoreRayIntersection() && overlay->isLoaded()) {
// FIXME: this ignores overlays with ignorePickIntersection == true, which seems wrong
if (overlay && overlay->getVisible() && !overlay->getIgnorePickIntersection() && overlay->isLoaded()) {
// get AABox in frame of overlay
glm::vec3 dimensions = overlay->getDimensions();
glm::vec3 low = dimensions * -0.5f;

View file

@ -59,19 +59,28 @@ class RayToOverlayIntersectionResult {
public:
bool intersects { false };
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
float distance { 0 };
float distance { 0.0f };
BoxFace face { UNKNOWN_FACE };
glm::vec3 surfaceNormal;
glm::vec3 intersection;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToOverlayIntersectionResult);
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value);
void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value);
class ParabolaToOverlayIntersectionResult {
public:
bool intersects { false };
OverlayID overlayID { UNKNOWN_OVERLAY_ID };
float distance { 0.0f };
float parabolicDistance { 0.0f };
BoxFace face { UNKNOWN_FACE };
glm::vec3 surfaceNormal;
glm::vec3 intersection;
QVariantMap extraInfo;
};
/**jsdoc
* The Overlays API provides facilities to create and interact with overlays. Overlays are 2D and 3D objects visible only to
* yourself and that aren't persisted to the domain. They are used for UI.
@ -110,6 +119,11 @@ public:
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
ParabolaToOverlayIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
const QVector<OverlayID>& overlaysToInclude,
const QVector<OverlayID>& overlaysToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
bool mousePressEvent(QMouseEvent* event);
bool mouseDoublePressEvent(QMouseEvent* event);
bool mouseReleaseEvent(QMouseEvent* event);

View file

@ -72,8 +72,48 @@ QVariant Planar3DOverlay::getProperty(const QString& property) {
bool Planar3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
// FIXME - face and surfaceNormal not being returned
return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getDimensions(), distance);
glm::vec2 xyDimensions = getDimensions();
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition();
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
}
return false;
}
bool Planar3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
glm::vec2 xyDimensions = getDimensions();
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition();
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
}
return false;
}
Transform Planar3DOverlay::evalRenderTransform() {

View file

@ -32,6 +32,8 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
protected:
glm::vec2 _dimensions;

View file

@ -140,8 +140,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -160,8 +160,7 @@ void Shape3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -60,8 +60,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) :
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -229,8 +229,7 @@ void Text3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.

View file

@ -88,7 +88,34 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
// and testing intersection there.
return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
bool hit = _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
if (hit) {
surfaceNormal = transform.getRotation() * surfaceNormal;
}
return hit;
}
bool Volume3DOverlay::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 worldToEntityMatrix;
Transform transform = getTransform();
transform.setScale(1.0f); // ignore any inherited scale from SpatiallyNestable
transform.getInverseMatrix(worldToEntityMatrix);
glm::vec3 overlayFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
glm::vec3 overlayFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
glm::vec3 overlayFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the overlays frame
// and testing intersection there.
bool hit = _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal);
if (hit) {
surfaceNormal = transform.getRotation() * surfaceNormal;
}
return hit;
}
Transform Volume3DOverlay::evalRenderTransform() {

View file

@ -32,6 +32,8 @@ public:
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
protected:
// Centered local bounding box

View file

@ -543,8 +543,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
* Antonyms: <code>isWire</code> and <code>wire</code>.
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
* <code>dashed</code>.
* @property {boolean} ignoreRayIntersection=false - If <code>true</code>,
* {@link Overlays.findRayIntersection|findRayIntersection} ignores the overlay.
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
* @property {boolean} grabbable=false - Signal to grabbing scripts whether or not this overlay can be grabbed.
@ -627,20 +626,6 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) {
}
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking) {
glm::vec2 dimensions = getDimensions();
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition();
if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) {
surfaceNormal = rotation * Vectors::UNIT_Z;
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
return true;
} else {
return false;
}
}
Web3DOverlay* Web3DOverlay::createClone() const {
return new Web3DOverlay(this);
}

View file

@ -52,9 +52,6 @@ public:
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, bool precisionPicking = false) override;
virtual Web3DOverlay* createClone() const override;
enum InputMode {

View file

@ -1568,6 +1568,7 @@ void Avatar::computeShapeInfo(ShapeInfo& shapeInfo) {
}
void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
// FIXME: this doesn't take into account Avatar rotation
ShapeInfo shapeInfo;
computeShapeInfo(shapeInfo);
glm::vec3 halfExtents = shapeInfo.getHalfExtents(); // x = radius, y = halfHeight

View file

@ -2555,15 +2555,18 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const {
return _controllerRightHandMatrixCache.get();
}
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
QScriptValue avatarIDValue = quuidToScriptValue(engine, value.avatarID);
obj.setProperty("avatarID", avatarIDValue);
obj.setProperty("distance", value.distance);
obj.setProperty("face", boxFaceToString(value.face));
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
return obj;
}
@ -2573,10 +2576,16 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
QScriptValue avatarIDValue = object.property("avatarID");
quuidFromScriptValue(avatarIDValue, value.avatarID);
value.distance = object.property("distance").toVariant().toFloat();
value.face = boxFaceFromString(object.property("face").toVariant().toString());
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);
}
QScriptValue surfaceNormal = object.property("surfaceNormal");
if (surfaceNormal.isValid()) {
vec3FromScriptValue(surfaceNormal, value.surfaceNormal);
}
value.extraInfo = object.property("extraInfo").toVariant().toMap();
}

View file

@ -1524,19 +1524,30 @@ void registerAvatarTypes(QScriptEngine* engine);
class RayToAvatarIntersectionResult {
public:
RayToAvatarIntersectionResult() : intersects(false), avatarID(), distance(0) {}
bool intersects;
bool intersects { false };
QUuid avatarID;
float distance;
float distance { 0.0f };
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToAvatarIntersectionResult)
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& results);
void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, RayToAvatarIntersectionResult& results);
class ParabolaToAvatarIntersectionResult {
public:
bool intersects { false };
QUuid avatarID;
float distance { 0.0f };
float parabolicDistance { 0.0f };
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(AvatarEntityMap)
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value);

View file

@ -29,6 +29,8 @@
#include <CursorManager.h>
#include <gl/GLWidget.h>
#include "GeometryUtil.h"
// Used to animate the magnification windows
//static const quint64 TOOLTIP_DELAY = 500 * MSECS_TO_USECS;
@ -357,9 +359,9 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
glm::vec3 localDirection = glm::normalize(transformVectorFast(worldToUi, direction));
const float UI_RADIUS = 1.0f;
float instersectionDistance;
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &instersectionDistance)) {
result = transformPoint(uiToWorld, localPosition + localDirection * instersectionDistance);
float intersectionDistance;
if (raySphereIntersect(localDirection, localPosition, UI_RADIUS, &intersectionDistance)) {
result = transformPoint(uiToWorld, localPosition + localDirection * intersectionDistance);
#ifdef WANT_DEBUG
DebugDraw::getInstance().drawRay(position, result, glm::vec4(0.0f, 1.0f, 0.0f, 1.0f));
#endif
@ -372,6 +374,23 @@ bool CompositorHelper::calculateRayUICollisionPoint(const glm::vec3& position, c
return false;
}
bool CompositorHelper::calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const {
glm::mat4 uiToWorld = getUiTransform();
glm::mat4 worldToUi = glm::inverse(uiToWorld);
glm::vec3 localOrigin = transformPoint(worldToUi, origin);
glm::vec3 localVelocity = glm::normalize(transformVectorFast(worldToUi, velocity));
glm::vec3 localAcceleration = glm::normalize(transformVectorFast(worldToUi, acceleration));
const float UI_RADIUS = 1.0f;
float intersectionDistance;
if (findParabolaSphereIntersection(localOrigin, localVelocity, localAcceleration, glm::vec3(0.0f), UI_RADIUS, intersectionDistance)) {
result = origin + velocity * intersectionDistance + 0.5f * acceleration * intersectionDistance * intersectionDistance;
parabolicDistance = intersectionDistance;
return true;
}
return false;
}
glm::vec2 CompositorHelper::sphericalToOverlay(const glm::vec2& sphericalPos) const {
glm::vec2 result = sphericalPos;
result.x *= -1.0f;

View file

@ -52,6 +52,7 @@ public:
void setRenderingWidget(QWidget* widget) { _renderingWidget = widget; }
bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const;
bool calculateParabolaUICollisionPoint(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, glm::vec3& result, float& parabolicDistance) const;
bool isHMD() const;
bool fakeEventActive() const { return _fakeMouseEvent; }

View file

@ -278,7 +278,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag
return properties;
}
bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
bool RenderableModelEntityItem::supportsDetailedIntersection() const {
return true;
}
@ -294,6 +294,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
face, surfaceNormal, extraInfo, precisionPicking, false);
}
bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face,
glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const {
auto model = getModel();
if (!model || !isModelLoaded()) {
return false;
}
return model->findParabolaIntersectionAgainstSubMeshes(origin, velocity, acceleration, parabolicDistance,
face, surfaceNormal, extraInfo, precisionPicking, false);
}
void RenderableModelEntityItem::getCollisionGeometryResource() {
QUrl hullURL(getCompoundShapeURL());
QUrlQuery queryArgs(hullURL);

View file

@ -66,11 +66,15 @@ public:
void doInitialModelSimulation();
void updateModelBounds();
virtual bool supportsDetailedRayIntersection() const override;
virtual bool supportsDetailedIntersection() const override;
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void setShapeType(ShapeType type) override;
virtual void setCompoundShapeURL(const QString& url) override;

View file

@ -567,8 +567,7 @@ public:
bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element,
float& distance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const
{
QVariantMap& extraInfo, bool precisionPicking) const {
// TODO -- correctly pick against marching-cube generated meshes
if (!precisionPicking) {
// just intersect with bounding box
@ -605,7 +604,6 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
voxelBox += result3 + Vectors::HALF;
float voxelDistance;
bool hit = voxelBox.findRayIntersection(glm::vec3(originInVoxel), glm::vec3(directionInVoxel),
voxelDistance, face, surfaceNormal);
@ -615,6 +613,87 @@ bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& o
return hit;
}
bool RenderablePolyVoxEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
// TODO -- correctly pick against marching-cube generated meshes
if (!precisionPicking) {
// just intersect with bounding box
return true;
}
glm::mat4 wtvMatrix = worldToVoxelMatrix();
glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f);
glm::vec4 velocityInVoxel = wtvMatrix * glm::vec4(velocity, 0.0f);
glm::vec4 accelerationInVoxel = wtvMatrix * glm::vec4(acceleration, 0.0f);
// find the first intersection with the voxel bounding box (slightly enlarged so we can catch voxels that touch the sides)
bool success;
glm::vec3 center = getCenterPosition(success);
glm::vec3 dimensions = getScaledDimensions();
const float FIRST_BOX_HALF_SCALE = 0.51f;
AABox voxelBox1(wtvMatrix * vec4(center - FIRST_BOX_HALF_SCALE * dimensions, 1.0f),
wtvMatrix * vec4(2.0f * FIRST_BOX_HALF_SCALE * dimensions, 0.0f));
bool hit1;
float parabolicDistance1;
// If we're starting inside the box, our first point is originInVoxel
if (voxelBox1.contains(originInVoxel)) {
parabolicDistance1 = 0.0f;
hit1 = true;
} else {
BoxFace face1;
glm::vec3 surfaceNormal1;
hit1 = voxelBox1.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel),
parabolicDistance1, face1, surfaceNormal1);
}
if (hit1) {
// find the second intersection, which should be with the inside of the box (use a slightly large box again)
const float SECOND_BOX_HALF_SCALE = 0.52f;
AABox voxelBox2(wtvMatrix * vec4(center - SECOND_BOX_HALF_SCALE * dimensions, 1.0f),
wtvMatrix * vec4(2.0f * SECOND_BOX_HALF_SCALE * dimensions, 0.0f));
glm::vec4 originInVoxel2 = originInVoxel + velocityInVoxel * parabolicDistance1 + 0.5f * accelerationInVoxel * parabolicDistance1 * parabolicDistance1;
glm::vec4 velocityInVoxel2 = velocityInVoxel + accelerationInVoxel * parabolicDistance1;
glm::vec4 accelerationInVoxel2 = accelerationInVoxel;
float parabolicDistance2;
BoxFace face2;
glm::vec3 surfaceNormal2;
// this should always be true
if (voxelBox2.findParabolaIntersection(glm::vec3(originInVoxel2), glm::vec3(velocityInVoxel2), glm::vec3(accelerationInVoxel2),
parabolicDistance2, face2, surfaceNormal2)) {
const int MAX_SECTIONS = 15;
PolyVox::RaycastResult raycastResult = PolyVox::RaycastResults::Completed;
glm::vec4 result = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
glm::vec4 segmentStartVoxel = originInVoxel2;
for (int i = 0; i < MAX_SECTIONS; i++) {
float t = parabolicDistance2 * ((float)(i + 1)) / ((float)MAX_SECTIONS);
glm::vec4 segmentEndVoxel = originInVoxel2 + velocityInVoxel2 * t + 0.5f * accelerationInVoxel2 * t * t;
raycastResult = doRayCast(segmentStartVoxel, segmentEndVoxel, result);
if (raycastResult != PolyVox::RaycastResults::Completed) {
// We hit something!
break;
}
segmentStartVoxel = segmentEndVoxel;
}
if (raycastResult == PolyVox::RaycastResults::Completed) {
// the parabola completed its path -- nothing was hit.
return false;
}
glm::vec3 result3 = glm::vec3(result);
AABox voxelBox;
voxelBox += result3 - Vectors::HALF;
voxelBox += result3 + Vectors::HALF;
return voxelBox.findParabolaIntersection(glm::vec3(originInVoxel), glm::vec3(velocityInVoxel), glm::vec3(accelerationInVoxel),
parabolicDistance, face, surfaceNormal);
}
}
return false;
}
PolyVox::RaycastResult RenderablePolyVoxEntityItem::doRayCast(glm::vec4 originInVoxel,
glm::vec4 farInVoxel,

View file

@ -51,11 +51,15 @@ public:
int getOnCount() const override { return _onCount; }
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const vec3& accleration,
OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void setVoxelData(const QByteArray& voxelData) override;
virtual void setVoxelVolumeSize(const glm::vec3& voxelVolumeSize) override;

View file

@ -159,11 +159,15 @@ public:
virtual void debugDump() const;
virtual bool supportsDetailedRayIntersection() const { return false; }
virtual bool supportsDetailedIntersection() const { return false; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const { return true; }
// attributes applicable to all entity types
EntityTypes::EntityType getType() const { return _type; }

View file

@ -871,6 +871,30 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke
return result;
}
ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
PROFILE_RANGE(script_entities, __FUNCTION__);
return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly);
}
ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola,
Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
ParabolaToEntityIntersectionResult result;
if (_entityTree) {
OctreeElementPointer element;
result.entityID = _entityTree->findParabolaIntersection(parabola,
entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking,
element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal,
result.extraInfo, lockType, &result.accurate);
result.intersects = !result.entityID.isNull();
}
return result;
}
bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) {
auto client = DependencyManager::get<EntityScriptClient>();
return client->reloadServerScript(entityID);
@ -1025,75 +1049,17 @@ bool EntityScriptingInterface::getDrawZoneBoundaries() const {
return ZoneEntityItem::getDrawZoneBoundaries();
}
RayToEntityIntersectionResult::RayToEntityIntersectionResult() :
intersects(false),
accurate(true), // assume it's accurate
entityID(),
distance(0),
face()
{
}
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
QScriptValue obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
obj.setProperty("accurate", value.accurate);
QScriptValue entityItemValue = EntityItemIDtoScriptValue(engine, value.entityID);
obj.setProperty("entityID", entityItemValue);
obj.setProperty("distance", value.distance);
QString faceName = "";
// handle BoxFace
/**jsdoc
* <p>A <code>BoxFace</code> specifies the face of an axis-aligned (AA) box.
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"MIN_X_FACE"</code></td><td>The minimum x-axis face.</td></tr>
* <tr><td><code>"MAX_X_FACE"</code></td><td>The maximum x-axis face.</td></tr>
* <tr><td><code>"MIN_Y_FACE"</code></td><td>The minimum y-axis face.</td></tr>
* <tr><td><code>"MAX_Y_FACE"</code></td><td>The maximum y-axis face.</td></tr>
* <tr><td><code>"MIN_Z_FACE"</code></td><td>The minimum z-axis face.</td></tr>
* <tr><td><code>"MAX_Z_FACE"</code></td><td>The maximum z-axis face.</td></tr>
* <tr><td><code>"UNKNOWN_FACE"</code></td><td>Unknown value.</td></tr>
* </tbody>
* </table>
* @typedef {string} BoxFace
*/
// FIXME: Move enum to string function to BoxBase.cpp.
switch (value.face) {
case MIN_X_FACE:
faceName = "MIN_X_FACE";
break;
case MAX_X_FACE:
faceName = "MAX_X_FACE";
break;
case MIN_Y_FACE:
faceName = "MIN_Y_FACE";
break;
case MAX_Y_FACE:
faceName = "MAX_Y_FACE";
break;
case MIN_Z_FACE:
faceName = "MIN_Z_FACE";
break;
case MAX_Z_FACE:
faceName = "MAX_Z_FACE";
break;
case UNKNOWN_FACE:
faceName = "UNKNOWN_FACE";
break;
}
obj.setProperty("face", faceName);
obj.setProperty("face", boxFaceToString(value.face));
QScriptValue intersection = vec3toScriptValue(engine, value.intersection);
obj.setProperty("intersection", intersection);
QScriptValue surfaceNormal = vec3toScriptValue(engine, value.surfaceNormal);
obj.setProperty("surfaceNormal", surfaceNormal);
obj.setProperty("extraInfo", engine->toScriptValue(value.extraInfo));
@ -1101,29 +1067,13 @@ QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, c
}
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& value) {
PROFILE_RANGE(script_entities, __FUNCTION__);
value.intersects = object.property("intersects").toVariant().toBool();
value.accurate = object.property("accurate").toVariant().toBool();
QScriptValue entityIDValue = object.property("entityID");
// EntityItemIDfromScriptValue(entityIDValue, value.entityID);
quuidFromScriptValue(entityIDValue, value.entityID);
value.distance = object.property("distance").toVariant().toFloat();
value.face = boxFaceFromString(object.property("face").toVariant().toString());
QString faceName = object.property("face").toVariant().toString();
if (faceName == "MIN_X_FACE") {
value.face = MIN_X_FACE;
} else if (faceName == "MAX_X_FACE") {
value.face = MAX_X_FACE;
} else if (faceName == "MIN_Y_FACE") {
value.face = MIN_Y_FACE;
} else if (faceName == "MAX_Y_FACE") {
value.face = MAX_Y_FACE;
} else if (faceName == "MIN_Z_FACE") {
value.face = MIN_Z_FACE;
} else {
value.face = MAX_Z_FACE;
};
QScriptValue intersection = object.property("intersection");
if (intersection.isValid()) {
vec3FromScriptValue(intersection, value.intersection);

View file

@ -71,22 +71,31 @@ private:
// "accurate" is currently always true because the ray intersection is always performed with an Octree::Lock.
class RayToEntityIntersectionResult {
public:
RayToEntityIntersectionResult();
bool intersects;
bool accurate;
bool intersects { false };
bool accurate { true };
QUuid entityID;
float distance;
float distance { 0.0f };
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(RayToEntityIntersectionResult)
QScriptValue RayToEntityIntersectionResultToScriptValue(QScriptEngine* engine, const RayToEntityIntersectionResult& results);
void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, RayToEntityIntersectionResult& results);
class ParabolaToEntityIntersectionResult {
public:
bool intersects { false };
bool accurate { true };
QUuid entityID;
float distance { 0.0f };
float parabolicDistance { 0.0f };
BoxFace face;
glm::vec3 intersection;
glm::vec3 surfaceNormal;
QVariantMap extraInfo;
};
/**jsdoc
* The Entities API provides facilities to create and interact with entities. Entities are 2D and 3D objects that are visible
@ -131,6 +140,12 @@ public:
void resetActivityTracking();
ActivityTracking getActivityTracking() const { return _activityTracking; }
// TODO: expose to script?
ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking,
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
bool visibleOnly, bool collidableOnly);
public slots:
/**jsdoc
@ -1895,6 +1910,11 @@ private:
bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
/// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode
ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType,
bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard,
bool visibleOnly = false, bool collidableOnly = false);
EntityTreePointer _entityTree;
std::recursive_mutex _entitiesScriptEngineLock;

View file

@ -63,6 +63,27 @@ public:
EntityItemID entityID;
};
class ParabolaArgs {
public:
// Inputs
glm::vec3 origin;
glm::vec3 velocity;
glm::vec3 acceleration;
const QVector<EntityItemID>& entityIdsToInclude;
const QVector<EntityItemID>& entityIdsToDiscard;
bool visibleOnly;
bool collidableOnly;
bool precisionPicking;
// Outputs
OctreeElementPointer& element;
float& parabolicDistance;
BoxFace& face;
glm::vec3& surfaceNormal;
QVariantMap& extraInfo;
EntityItemID entityID;
};
EntityTree::EntityTree(bool shouldReaverage) :
Octree(shouldReaverage)
@ -820,8 +841,7 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm:
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
Octree::lockType lockType, bool* accurateResult) {
RayArgs args = { origin, direction, entityIdsToInclude, entityIdsToDiscard,
visibleOnly, collidableOnly, precisionPicking,
element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() };
distance = FLT_MAX;
bool requireLock = lockType == Octree::Lock;
@ -836,6 +856,47 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm:
return args.entityID;
}
bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) {
ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData);
bool keepSearching = true;
EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element);
EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, keepSearching,
args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude,
args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking);
if (!entityID.isNull()) {
args->entityID = entityID;
}
return keepSearching;
}
EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola,
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
bool visibleOnly, bool collidableOnly, bool precisionPicking,
OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
Octree::lockType lockType, bool* accurateResult) {
ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard,
visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() };
parabolicDistance = FLT_MAX;
distance = FLT_MAX;
bool requireLock = lockType == Octree::Lock;
bool lockResult = withReadLock([&] {
recurseTreeWithOperation(findParabolaIntersectionOp, &args);
}, requireLock);
if (accurateResult) {
*accurateResult = lockResult; // if user asked to accuracy or result, let them know this is accurate
}
if (!args.entityID.isNull()) {
intersection = parabola.origin + parabola.velocity * parabolicDistance + 0.5f * parabola.acceleration * parabolicDistance * parabolicDistance;
distance = glm::distance(intersection, parabola.origin);
}
return args.entityID;
}
EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) {
FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX };

View file

@ -95,7 +95,14 @@ public:
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
bool visibleOnly, bool collidableOnly, bool precisionPicking,
OctreeElementPointer& node, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);
virtual EntityItemID findParabolaIntersection(const PickParabola& parabola,
QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard,
bool visibleOnly, bool collidableOnly, bool precisionPicking,
OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL);

View file

@ -159,7 +159,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
}
// by default, we only allow intersections with leaves with content
if (!canRayIntersect()) {
if (!canPickIntersect()) {
return result; // we don't intersect with non-leaves, and we keep searching
}
@ -168,7 +168,7 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con
QVariantMap localExtraInfo;
float distanceToElementDetails = distance;
EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails,
face, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
localExtraInfo, precisionPicking);
if (!entityID.isNull() && distanceToElementDetails < distance) {
distance = distanceToElementDetails;
@ -232,7 +232,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
localFace, localSurfaceNormal)) {
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < distance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedRayIntersection()) {
if (entity->supportsDetailedIntersection()) {
QVariantMap localExtraInfo;
if (entity->findDetailedRayIntersection(origin, direction, element, localDistance,
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
@ -250,7 +250,8 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori
if (localDistance < distance && entity->getType() != EntityTypes::ParticleEffect) {
distance = localDistance;
face = localFace;
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 1.0f));
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f));
extraInfo = QVariantMap();
entityID = entity->getEntityItemID();
}
}
@ -287,6 +288,144 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
return result;
}
EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking) {
EntityItemID result;
float distanceToElementCube = std::numeric_limits<float>::max();
BoxFace localFace;
glm::vec3 localSurfaceNormal;
// if the parabola doesn't intersect with our cube OR the distance to element is less than current best distance
// we can stop searching!
bool hit = _cube.findParabolaIntersection(origin, velocity, acceleration, distanceToElementCube, localFace, localSurfaceNormal);
if (!hit || (!_cube.contains(origin) && distanceToElementCube > parabolicDistance)) {
keepSearching = false; // no point in continuing to search
return result; // we did not intersect
}
// by default, we only allow intersections with leaves with content
if (!canPickIntersect()) {
return result; // we don't intersect with non-leaves, and we keep searching
}
// if the distance to the element cube is not less than the current best distance, then it's not possible
// for any details inside the cube to be closer so we don't need to consider them.
QVariantMap localExtraInfo;
float distanceToElementDetails = parabolicDistance;
// We can precompute the world-space parabola normal and reuse it for the parabola plane intersects AABox sphere check
glm::vec3 vectorOnPlane = velocity;
if (glm::dot(glm::normalize(velocity), glm::normalize(acceleration)) > 1.0f - EPSILON) {
// Handle the degenerate case where velocity is parallel to acceleration
// We pick t = 1 and calculate a second point on the plane
vectorOnPlane = velocity + 0.5f * acceleration;
}
// Get the normal of the plane, the cross product of two vectors on the plane
glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration));
EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails,
localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly,
localExtraInfo, precisionPicking);
if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) {
parabolicDistance = distanceToElementDetails;
face = localFace;
surfaceNormal = localSurfaceNormal;
extraInfo = localExtraInfo;
result = entityID;
}
return result;
}
EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal,
const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard,
bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) {
// only called if we do intersect our bounding cube, but find if we actually intersect with entities...
int entityNumber = 0;
EntityItemID entityID;
forEachEntity([&](EntityItemPointer entity) {
// use simple line-sphere for broadphase check
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
AABox entityBox = entity->getAABox(success);
if (!success) {
return;
}
// Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane
// defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic,
// the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection
// below
if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) {
return;
}
// check RayPick filter settings
if ((visibleOnly && !entity->isVisible())
|| (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE))
|| (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID()))
|| (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) {
return;
}
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation());
glm::mat4 translation = glm::translate(entity->getWorldPosition());
glm::mat4 entityToWorldMatrix = translation * rotation;
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 dimensions = entity->getRaycastDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint();
glm::vec3 corner = -(dimensions * registrationPoint);
AABox entityFrameBox(corner, dimensions);
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the entity frame
// and testing intersection there.
float localDistance;
BoxFace localFace;
glm::vec3 localSurfaceNormal;
if (entityFrameBox.findParabolaIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, localDistance,
localFace, localSurfaceNormal)) {
if (entityFrameBox.contains(entityFrameOrigin) || localDistance < parabolicDistance) {
// now ask the entity if we actually intersect
if (entity->supportsDetailedIntersection()) {
QVariantMap localExtraInfo;
if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance,
localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) {
if (localDistance < parabolicDistance) {
parabolicDistance = localDistance;
face = localFace;
surfaceNormal = localSurfaceNormal;
extraInfo = localExtraInfo;
entityID = entity->getEntityItemID();
}
}
} else {
// if the entity type doesn't support a detailed intersection, then just return the non-AABox results
// Never intersect with particle entities
if (localDistance < parabolicDistance && entity->getType() != EntityTypes::ParticleEffect) {
parabolicDistance = localDistance;
face = localFace;
surfaceNormal = glm::vec3(rotation * glm::vec4(localSurfaceNormal, 0.0f));
extraInfo = QVariantMap();
entityID = entity->getEntityItemID();
}
}
}
}
entityNumber++;
});
return entityID;
}
EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const {
EntityItemPointer closestEntity = NULL;
float closestEntityDistance = FLT_MAX;

View file

@ -134,9 +134,9 @@ public:
virtual bool isRendered() const override { return getShouldRender(); }
virtual bool deleteApproved() const override { return !hasEntities(); }
virtual bool canRayIntersect() const override { return hasEntities(); }
virtual bool canPickIntersect() const override { return hasEntities(); }
virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
bool& keepSearching, OctreeElementPointer& node, float& distance,
bool& keepSearching, OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking = false);
@ -148,6 +148,16 @@ public:
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const override;
virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, bool& keepSearching, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking = false);
virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude,
const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly,
QVariantMap& extraInfo, bool precisionPicking);
template <typename F>
void forEachEntity(F f) const {

View file

@ -309,3 +309,14 @@ bool LightEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
return _lightsArePickable;
}
bool LightEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
// TODO: consider if this is really what we want to do. We've made it so that "lights are pickable" is a global state
// this is probably reasonable since there's typically only one tree you'd be picking on at a time. Technically we could
// be on the clipboard and someone might be trying to use the parabola intersection API there. Anyway... if you ever try to
// do parabola intersection testing off of trees other than the main tree of the main entity renderer, then we'll need to
// fix this mechanism.
return _lightsArePickable;
}

View file

@ -84,11 +84,15 @@ public:
bool lightPropertiesChanged() const { return _lightPropertiesChanged; }
void resetLightPropertiesChanged();
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
private:
// properties of a light

View file

@ -58,12 +58,17 @@ class LineEntityItem : public EntityItem {
virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; }
// never have a ray intersection pick a LineEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo,
bool precisionPicking) const override { return false; }
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo,
bool precisionPicking) const override { return false; }
bool pointsChanged() const { return _pointsChanged; }
void resetPointsChanged();
virtual void debugDump() const override;

View file

@ -340,7 +340,7 @@ public:
bool getEmitterShouldTrail() const { return _particleProperties.emission.shouldTrail; }
void setEmitterShouldTrail(bool emitterShouldTrail);
virtual bool supportsDetailedRayIntersection() const override { return false; }
virtual bool supportsDetailedIntersection() const override { return false; }
particle::Properties getParticleProperties() const;

View file

@ -89,11 +89,15 @@ class PolyLineEntityItem : public EntityItem {
// never have a ray intersection pick a PolyLineEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
// disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain
virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious!

View file

@ -42,11 +42,15 @@ class PolyVoxEntityItem : public EntityItem {
bool& somethingChanged) override;
// never have a ray intersection pick a PolyVoxEntityItem.
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override { return false; }
virtual void debugDump() const override;

View file

@ -250,7 +250,7 @@ void ShapeEntityItem::setUnscaledDimensions(const glm::vec3& value) {
}
}
bool ShapeEntityItem::supportsDetailedRayIntersection() const {
bool ShapeEntityItem::supportsDetailedIntersection() const {
return _shape == entity::Sphere;
}
@ -273,6 +273,7 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
glm::vec3 hitAt = glm::vec3(entityToWorldMatrix * glm::vec4(entityFrameHitAt, 1.0f));
distance = glm::distance(origin, hitAt);
bool success;
// FIXME: this is only correct for uniformly scaled spheres
surfaceNormal = glm::normalize(hitAt - getCenterPosition(success));
if (!success) {
return false;
@ -282,6 +283,30 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
return false;
}
bool ShapeEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
// determine the parabola in the frame of the entity transformed from a unit sphere
glm::mat4 entityToWorldMatrix = getEntityToWorldMatrix();
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 entityFrameOrigin = glm::vec3(worldToEntityMatrix * glm::vec4(origin, 1.0f));
glm::vec3 entityFrameVelocity = glm::vec3(worldToEntityMatrix * glm::vec4(velocity, 0.0f));
glm::vec3 entityFrameAcceleration = glm::vec3(worldToEntityMatrix * glm::vec4(acceleration, 0.0f));
// NOTE: unit sphere has center of 0,0,0 and radius of 0.5
if (findParabolaSphereIntersection(entityFrameOrigin, entityFrameVelocity, entityFrameAcceleration, glm::vec3(0.0f), 0.5f, parabolicDistance)) {
bool success;
// FIXME: this is only correct for uniformly scaled spheres
surfaceNormal = glm::normalize((origin + velocity * parabolicDistance + 0.5f * acceleration * parabolicDistance * parabolicDistance) - getCenterPosition(success));
if (!success) {
return false;
}
return true;
}
return false;
}
void ShapeEntityItem::debugDump() const {
quint64 now = usecTimestampNow();
qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------";

View file

@ -90,11 +90,15 @@ public:
bool shouldBePhysical() const override { return !isDead(); }
bool supportsDetailedRayIntersection() const override;
bool supportsDetailedIntersection() const override;
bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
void debugDump() const override;

View file

@ -127,17 +127,55 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
}
bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition() + rotation *
(dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
// FIXME - should set face and surfaceNormal
return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance);
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
}
return false;
}
bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
}
return false;
}
void TextEntityItem::setText(const QString& value) {

View file

@ -45,11 +45,15 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
static const QString DEFAULT_TEXT;
void setText(const QString& value);

View file

@ -113,8 +113,44 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) {
surfaceNormal = rotation * Vectors::UNIT_Z;
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = forward;
}
return true;
} else {
return false;
}
}
bool WebEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
glm::vec3 dimensions = getScaledDimensions();
glm::vec2 xyDimensions(dimensions.x, dimensions.y);
glm::quat rotation = getWorldOrientation();
glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()));
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - position);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) {
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance;
glm::vec3 forward = rotation * Vectors::FRONT;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = forward;
} else {
face = MAX_Z_FACE;
surfaceNormal = -forward;
}
return true;
} else {
return false;

View file

@ -44,11 +44,15 @@ public:
EntityPropertyFlags& propertyFlags, bool overwriteLocalData,
bool& somethingChanged) override;
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void setSourceUrl(const QString& value);
QString getSourceUrl() const;

View file

@ -294,10 +294,16 @@ void ZoneEntityItem::setCompoundShapeURL(const QString& url) {
}
bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
return _zonesArePickable;
}
bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const {
return _zonesArePickable;
}

View file

@ -102,11 +102,15 @@ public:
void resetRenderingPropertiesChanged();
virtual bool supportsDetailedRayIntersection() const override { return true; }
virtual bool supportsDetailedIntersection() const override { return true; }
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool precisionPicking) const override;
virtual void debugDump() const override;

View file

@ -99,7 +99,7 @@ public:
virtual bool deleteApproved() const { return true; }
virtual bool canRayIntersect() const { return isLeaf(); }
virtual bool canPickIntersect() const { return isLeaf(); }
/// \param center center of sphere in meters
/// \param radius radius of sphere in meters
/// \param[out] penetration pointing into cube from sphere

View file

@ -161,6 +161,7 @@ public:
enum PickType {
Ray = 0,
Stylus,
Parabola,
NUM_PICK_TYPES
};

View file

@ -100,6 +100,7 @@ void PickManager::update() {
// and the rayPicks updae will ALWAYS update at least one ray even when there is no budget
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
_parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD);
}
bool PickManager::isLeftHand(unsigned int uid) {

View file

@ -59,14 +59,15 @@ protected:
std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 };
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 };
std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
unsigned int _nextPickID { INVALID_PICK_ID + 1 };
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
PickCacheOptimizer<PickParabola> _parabolaPickCacheOptimizer;
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC;
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC;
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };
};

View file

@ -353,31 +353,27 @@ void Model::initJointStates() {
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
bool pickAgainstTriangles, bool allowBackface) {
bool intersectedSomething = false;
// if we aren't active, we can't ray pick yet...
// if we aren't active, we can't pick yet...
if (!isActive()) {
return intersectedSomething;
}
// extents is the entity relative, scaled, centered extents of the entity
glm::vec3 position = _translation;
glm::mat4 rotation = glm::mat4_cast(_rotation);
glm::mat4 translation = glm::translate(position);
glm::mat4 modelToWorldMatrix = translation * rotation;
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation);
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum;
glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the ray picking in the model frame of reference
glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference
AABox modelFrameBox(corner, dimensions);
glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f));
glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f));
// we can use the AABox's ray intersection by mapping our origin and direction into the model frame
// we can use the AABox's intersection by mapping our origin and direction into the model frame
// and testing intersection there.
if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, distance, face, surfaceNormal)) {
QMutexLocker locker(&_mutex);
@ -395,7 +391,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
}
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix;
glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix;
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@ -405,11 +401,10 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
int partIndex = 0;
for (auto &partTriangleSet : meshTriangleSets) {
float triangleSetDistance = 0.0f;
float triangleSetDistance;
BoxFace triangleSetFace;
Triangle triangleSetTriangle;
if (partTriangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
float worldDistance = glm::distance(origin, worldIntersectionPoint);
@ -457,6 +452,111 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
return intersectedSomething;
}
bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo,
bool pickAgainstTriangles, bool allowBackface) {
bool intersectedSomething = false;
// if we aren't active, we can't pick yet...
if (!isActive()) {
return intersectedSomething;
}
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation);
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum;
glm::vec3 corner = -(dimensions * _registrationPoint); // since we're going to do the picking in the model frame of reference
AABox modelFrameBox(corner, dimensions);
glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f));
glm::vec3 modelFrameVelocity = glm::vec3(worldToModelMatrix * glm::vec4(velocity, 0.0f));
glm::vec3 modelFrameAcceleration = glm::vec3(worldToModelMatrix * glm::vec4(acceleration, 0.0f));
// we can use the AABox's intersection by mapping our origin and direction into the model frame
// and testing intersection there.
if (modelFrameBox.findParabolaIntersection(modelFrameOrigin, modelFrameVelocity, modelFrameAcceleration, parabolicDistance, face, surfaceNormal)) {
QMutexLocker locker(&_mutex);
float bestDistance = FLT_MAX;
Triangle bestModelTriangle;
Triangle bestWorldTriangle;
int bestSubMeshIndex = 0;
int subMeshIndex = 0;
const FBXGeometry& geometry = getFBXGeometry();
if (!_triangleSetsValid) {
calculateTriangleSets(geometry);
}
glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix;
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
glm::vec3 meshFrameVelocity = glm::vec3(worldToMeshMatrix * glm::vec4(velocity, 0.0f));
glm::vec3 meshFrameAcceleration = glm::vec3(worldToMeshMatrix * glm::vec4(acceleration, 0.0f));
int shapeID = 0;
for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) {
int partIndex = 0;
for (auto &partTriangleSet : meshTriangleSets) {
float triangleSetDistance;
BoxFace triangleSetFace;
Triangle triangleSetTriangle;
if (partTriangleSet.findParabolaIntersection(meshFrameOrigin, meshFrameVelocity, meshFrameAcceleration,
triangleSetDistance, triangleSetFace, triangleSetTriangle, pickAgainstTriangles, allowBackface)) {
if (triangleSetDistance < bestDistance) {
bestDistance = triangleSetDistance;
intersectedSomething = true;
face = triangleSetFace;
bestModelTriangle = triangleSetTriangle;
bestWorldTriangle = triangleSetTriangle * meshToWorldMatrix;
glm::vec3 meshIntersectionPoint = meshFrameOrigin + meshFrameVelocity * triangleSetDistance +
0.5f * meshFrameAcceleration * triangleSetDistance * triangleSetDistance;
glm::vec3 worldIntersectionPoint = origin + velocity * triangleSetDistance +
0.5f * acceleration * triangleSetDistance * triangleSetDistance;
extraInfo["worldIntersectionPoint"] = vec3toVariant(worldIntersectionPoint);
extraInfo["meshIntersectionPoint"] = vec3toVariant(meshIntersectionPoint);
extraInfo["partIndex"] = partIndex;
extraInfo["shapeID"] = shapeID;
bestSubMeshIndex = subMeshIndex;
}
}
partIndex++;
shapeID++;
}
subMeshIndex++;
}
if (intersectedSomething) {
parabolicDistance = bestDistance;
surfaceNormal = bestWorldTriangle.getNormal();
if (pickAgainstTriangles) {
extraInfo["subMeshIndex"] = bestSubMeshIndex;
extraInfo["subMeshName"] = geometry.getModelNameOfMesh(bestSubMeshIndex);
extraInfo["subMeshTriangleWorld"] = QVariantMap{
{ "v0", vec3toVariant(bestWorldTriangle.v0) },
{ "v1", vec3toVariant(bestWorldTriangle.v1) },
{ "v2", vec3toVariant(bestWorldTriangle.v2) },
};
extraInfo["subMeshNormal"] = vec3toVariant(bestModelTriangle.getNormal());
extraInfo["subMeshTriangle"] = QVariantMap{
{ "v0", vec3toVariant(bestModelTriangle.v0) },
{ "v1", vec3toVariant(bestModelTriangle.v1) },
{ "v2", vec3toVariant(bestModelTriangle.v2) },
};
}
}
}
return intersectedSomething;
}
bool Model::convexHullContains(glm::vec3 point) {
// if we aren't active, we can't compute that yet...
if (!isActive()) {
@ -594,7 +694,7 @@ bool Model::replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointe
}
scene->enqueueTransaction(transaction);
}
// update triangles for ray picking
// update triangles for picking
{
FBXGeometry geometry;
for (const auto& newMesh : meshes) {

View file

@ -178,6 +178,9 @@ public:
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
bool findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal,
QVariantMap& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }

View file

@ -0,0 +1,18 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Created by Sam Gondelman on 7/18/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
//
in vec4 _color;
out vec4 _fragColor;
void main(void) {
_fragColor = _color;
}

View file

@ -0,0 +1,54 @@
<@include gpu/Config.slh@>
<$VERSION_HEADER$>
// Generated on <$_SCRIBE_DATE$>
//
// Created by Sam Gondelman on 7/18/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 gpu/Transform.slh@>
<$declareStandardTransform()$>
layout(std140) uniform parabolaData {
vec3 velocity;
float parabolicDistance;
vec3 acceleration;
float width;
vec4 color;
int numSections;
ivec3 spare;
};
out vec4 _color;
void main(void) {
_color = color;
float t = parabolicDistance * (floor(gl_VertexID / 2) / float(numSections));
vec4 pos = vec4(velocity * t + 0.5 * acceleration * t * t, 1);
const float EPSILON = 0.00001;
vec4 normal;
TransformCamera cam = getTransformCamera();
TransformObject obj = getTransformObject();
if (dot(acceleration, acceleration) < EPSILON) {
// Handle case where acceleration == (0, 0, 0)
vec3 eyeUp = vec3(0, 1, 0);
vec3 worldUp;
<$transformEyeToWorldDir(cam, eyeUp, worldUp)$>
normal = vec4(normalize(cross(velocity, worldUp)), 0);
} else {
normal = vec4(normalize(cross(velocity, acceleration)), 0);
}
if (gl_VertexID % 2 == 0) {
pos += 0.5 * width * normal;
} else {
pos -= 0.5 * width * normal;
}
<$transformModelToClipPos(cam, obj, pos, gl_Position)$>
}

View file

@ -109,19 +109,12 @@ glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const {
return result;
}
// determines whether a value is within the extents
static bool isWithin(float value, float corner, float size) {
return value >= corner && value <= corner + size;
}
bool AABox::contains(const Triangle& triangle) const {
return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2);
}
bool AABox::contains(const glm::vec3& point) const {
return isWithin(point.x, _corner.x, _scale.x) &&
isWithin(point.y, _corner.y, _scale.y) &&
isWithin(point.z, _corner.z, _scale.z);
return aaBoxContains(point, _corner, _scale);
}
bool AABox::contains(const AABox& otherBox) const {
@ -175,30 +168,6 @@ bool AABox::expandedContains(const glm::vec3& point, float expansion) const {
isWithinExpanded(point.z, _corner.z, _scale.z, expansion);
}
// finds the intersection between a ray and the facing plane on one axis
static bool findIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = (corner - origin) / direction;
return true;
} else if (direction < -EPSILON) {
distance = (corner + size - origin) / direction;
return true;
}
return false;
}
// finds the intersection between a ray and the inside facing plane on one axis
static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = -1.0f * (origin - (corner + size)) / direction;
return true;
} else if (direction < -EPSILON) {
distance = -1.0f * (origin - corner) / direction;
return true;
}
return false;
}
bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
// handle the trivial cases where the expanded box contains the start or end
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
@ -225,66 +194,12 @@ bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& e
bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const {
// handle the trivial case where the box contains the origin
if (contains(origin)) {
// We still want to calculate the distance from the origin to the inside out plane
float axisDistance;
if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
distance = axisDistance;
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
distance = axisDistance;
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) {
distance = axisDistance;
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f);
return true;
}
// This case is unexpected, but mimics the previous behavior for inside out intersections
distance = 0;
return true;
}
return findRayAABoxIntersection(origin, direction, _corner, _scale, distance, face, surfaceNormal);
}
// check each axis
float axisDistance;
if ((findIntersection(origin.x, direction.x, _corner.x, _scale.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
distance = axisDistance;
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f);
return true;
}
if ((findIntersection(origin.y, direction.y, _corner.y, _scale.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale.z))) {
distance = axisDistance;
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f);
return true;
}
if ((findIntersection(origin.z, direction.z, _corner.z, _scale.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale.y) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale.x))) {
distance = axisDistance;
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f);
return true;
}
return false;
bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, _scale, parabolicDistance, face, surfaceNormal);
}
bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const {
@ -296,6 +211,29 @@ bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& dire
|| (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared));
}
bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const {
glm::vec3 localCenter = calcCenter() - origin;
const float ONE_OVER_TWO_SQUARED = 0.25f;
float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale);
// origin is inside the sphere
if (glm::length2(localCenter) < radiusSquared) {
return true;
}
if (glm::length2(acceleration) < EPSILON) {
// Handle the degenerate case where acceleration == (0, 0, 0)
return rayHitsBoundingSphere(origin, glm::normalize(velocity));
} else {
// Project vector from plane to sphere center onto the normal
float distance = glm::dot(localCenter, normal);
if (distance * distance < radiusSquared) {
return true;
}
}
return false;
}
bool AABox::touchesSphere(const glm::vec3& center, float radius) const {
// Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf
glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO);

View file

@ -70,8 +70,11 @@ public:
bool expandedContains(const glm::vec3& point, float expansion) const;
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const;
BoxFace& face, glm::vec3& surfaceNormal) const;
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
bool rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const;
bool parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const;
bool touchesSphere(const glm::vec3& center, float radius) const; // fast but may generate false positives
bool touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
@ -136,6 +139,9 @@ private:
static BoxFace getOppositeFace(BoxFace face);
void checkPossibleParabolicIntersection(float t, int i, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const;
glm::vec3 _corner;
glm::vec3 _scale;
};

View file

@ -110,15 +110,8 @@ glm::vec3 AACube::getNearestVertex(const glm::vec3& normal) const {
return result;
}
// determines whether a value is within the extents
static bool isWithin(float value, float corner, float size) {
return value >= corner && value <= corner + size;
}
bool AACube::contains(const glm::vec3& point) const {
return isWithin(point.x, _corner.x, _scale) &&
isWithin(point.y, _corner.y, _scale) &&
isWithin(point.z, _corner.z, _scale);
return aaBoxContains(point, _corner, glm::vec3(_scale));
}
bool AACube::contains(const AACube& otherCube) const {
@ -170,30 +163,6 @@ bool AACube::expandedContains(const glm::vec3& point, float expansion) const {
isWithinExpanded(point.z, _corner.z, _scale, expansion);
}
// finds the intersection between a ray and the facing plane on one axis
static bool findIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = (corner - origin) / direction;
return true;
} else if (direction < -EPSILON) {
distance = (corner + size - origin) / direction;
return true;
}
return false;
}
// finds the intersection between a ray and the inside facing plane on one axis
static bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = -1.0f * (origin - (corner + size)) / direction;
return true;
} else if (direction < -EPSILON) {
distance = -1.0f * (origin - corner) / direction;
return true;
}
return false;
}
bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const {
// handle the trivial cases where the expanded box contains the start or end
if (expandedContains(start, expansion) || expandedContains(end, expansion)) {
@ -220,67 +189,12 @@ bool AACube::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3&
bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const {
// handle the trivial case where the box contains the origin
if (contains(origin)) {
return findRayAABoxIntersection(origin, direction, _corner, glm::vec3(_scale), distance, face, surfaceNormal);
}
// We still want to calculate the distance from the origin to the inside out plane
float axisDistance;
if ((findInsideOutIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
distance = axisDistance;
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
distance = axisDistance;
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) {
distance = axisDistance;
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f);
return true;
}
// This case is unexpected, but mimics the previous behavior for inside out intersections
distance = 0;
return true;
}
// check each axis
float axisDistance;
if ((findIntersection(origin.x, direction.x, _corner.x, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
distance = axisDistance;
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f);
return true;
}
if ((findIntersection(origin.y, direction.y, _corner.y, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale) &&
isWithin(origin.z + axisDistance*direction.z, _corner.z, _scale))) {
distance = axisDistance;
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f);
return true;
}
if ((findIntersection(origin.z, direction.z, _corner.z, _scale, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance*direction.y, _corner.y, _scale) &&
isWithin(origin.x + axisDistance*direction.x, _corner.x, _scale))) {
distance = axisDistance;
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f);
return true;
}
return false;
bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, glm::vec3(_scale), parabolicDistance, face, surfaceNormal);
}
bool AACube::touchesSphere(const glm::vec3& center, float radius) const {

View file

@ -58,6 +58,8 @@ public:
bool expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const;
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) const;
bool findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const;
bool touchesSphere(const glm::vec3& center, float radius) const;
bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const;
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const;

View file

@ -0,0 +1,46 @@
//
// Created by Sam Gondelman on 7/20/18
// 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 "BoxBase.h"
QString boxFaceToString(BoxFace face) {
switch (face) {
case MIN_X_FACE:
return "MIN_X_FACE";
case MAX_X_FACE:
return "MAX_X_FACE";
case MIN_Y_FACE:
return "MIN_Y_FACE";
case MAX_Y_FACE:
return "MAX_Y_FACE";
case MIN_Z_FACE:
return "MIN_Z_FACE";
case MAX_Z_FACE:
return "MAX_Z_FACE";
default:
return "UNKNOWN_FACE";
}
}
BoxFace boxFaceFromString(const QString& face) {
if (face == "MIN_X_FACE") {
return MIN_X_FACE;
} else if (face == "MAX_X_FACE") {
return MAX_X_FACE;
} else if (face == "MIN_Y_FACE") {
return MIN_Y_FACE;
} else if (face == "MAX_Y_FACE") {
return MAX_Y_FACE;
} else if (face == "MIN_Z_FACE") {
return MIN_Z_FACE;
} else if (face == "MAX_Z_FACE") {
return MAX_Z_FACE;
} else {
return UNKNOWN_FACE;
}
}

View file

@ -16,7 +16,26 @@
#define hifi_BoxBase_h
#include <glm/glm.hpp>
#include <QString>
/**jsdoc
* <p>A <code>BoxFace</code> specifies the face of an axis-aligned (AA) box.
* <table>
* <thead>
* <tr><th>Value</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"MIN_X_FACE"</code></td><td>The minimum x-axis face.</td></tr>
* <tr><td><code>"MAX_X_FACE"</code></td><td>The maximum x-axis face.</td></tr>
* <tr><td><code>"MIN_Y_FACE"</code></td><td>The minimum y-axis face.</td></tr>
* <tr><td><code>"MAX_Y_FACE"</code></td><td>The maximum y-axis face.</td></tr>
* <tr><td><code>"MIN_Z_FACE"</code></td><td>The minimum z-axis face.</td></tr>
* <tr><td><code>"MAX_Z_FACE"</code></td><td>The maximum z-axis face.</td></tr>
* <tr><td><code>"UNKNOWN_FACE"</code></td><td>Unknown value.</td></tr>
* </tbody>
* </table>
* @typedef {string} BoxFace
*/
enum BoxFace {
MIN_X_FACE,
MAX_X_FACE,
@ -27,6 +46,9 @@ enum BoxFace {
UNKNOWN_FACE
};
QString boxFaceToString(BoxFace face);
BoxFace boxFaceFromString(const QString& face);
enum BoxVertex {
BOTTOM_LEFT_NEAR = 0,
BOTTOM_RIGHT_NEAR = 1,

View file

@ -1,4 +1,4 @@
//
//
// GeometryUtil.cpp
// libraries/shared/src
//
@ -15,7 +15,10 @@
#include <cstring>
#include <cmath>
#include <bitset>
#include <complex>
#include <qmath.h>
#include <glm/gtx/quaternion.hpp>
#include "glm/gtc/matrix_transform.hpp"
#include "NumericalConstants.h"
#include "GLMHelpers.h"
@ -187,6 +190,94 @@ glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3&
newPenetration - (currentDirection * directionalComponent);
}
// finds the intersection between a ray and the facing plane on one axis
bool findIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = (corner - origin) / direction;
return true;
} else if (direction < -EPSILON) {
distance = (corner + size - origin) / direction;
return true;
}
return false;
}
// finds the intersection between a ray and the inside facing plane on one axis
bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) {
if (direction > EPSILON) {
distance = -1.0f * (origin - (corner + size)) / direction;
return true;
} else if (direction < -EPSILON) {
distance = -1.0f * (origin - corner) / direction;
return true;
}
return false;
}
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) {
// handle the trivial case where the box contains the origin
if (aaBoxContains(origin, corner, scale)) {
// We still want to calculate the distance from the origin to the inside out plane
float axisDistance;
if ((findInsideOutIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
distance = axisDistance;
face = direction.x > 0 ? MAX_X_FACE : MIN_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? 1.0f : -1.0f, 0.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) &&
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
distance = axisDistance;
face = direction.y > 0 ? MAX_Y_FACE : MIN_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? 1.0f : -1.0f, 0.0f);
return true;
}
if ((findInsideOutIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) {
distance = axisDistance;
face = direction.z > 0 ? MAX_Z_FACE : MIN_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? 1.0f : -1.0f);
return true;
}
// This case is unexpected, but mimics the previous behavior for inside out intersections
distance = 0;
return true;
}
// check each axis
float axisDistance;
if ((findIntersection(origin.x, direction.x, corner.x, scale.x, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
distance = axisDistance;
face = direction.x > 0 ? MIN_X_FACE : MAX_X_FACE;
surfaceNormal = glm::vec3(direction.x > 0 ? -1.0f : 1.0f, 0.0f, 0.0f);
return true;
}
if ((findIntersection(origin.y, direction.y, corner.y, scale.y, axisDistance) && axisDistance >= 0 &&
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x) &&
isWithin(origin.z + axisDistance * direction.z, corner.z, scale.z))) {
distance = axisDistance;
face = direction.y > 0 ? MIN_Y_FACE : MAX_Y_FACE;
surfaceNormal = glm::vec3(0.0f, direction.y > 0 ? -1.0f : 1.0f, 0.0f);
return true;
}
if ((findIntersection(origin.z, direction.z, corner.z, scale.z, axisDistance) && axisDistance >= 0 &&
isWithin(origin.y + axisDistance * direction.y, corner.y, scale.y) &&
isWithin(origin.x + axisDistance * direction.x, corner.x, scale.x))) {
distance = axisDistance;
face = direction.z > 0 ? MIN_Z_FACE : MAX_Z_FACE;
surfaceNormal = glm::vec3(0.0f, 0.0f, direction.z > 0 ? -1.0f : 1.0f);
return true;
}
return false;
}
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& center, float radius, float& distance) {
glm::vec3 relativeOrigin = origin - center;
@ -711,6 +802,658 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire
return false;
}
// determines whether a value is within the extents
bool isWithin(float value, float corner, float size) {
return value >= corner && value <= corner + size;
}
bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale) {
return isWithin(point.x, corner.x, scale.x) &&
isWithin(point.y, corner.y, scale.y) &&
isWithin(point.z, corner.z, scale.z);
}
void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) {
if (t < minDistance && t > 0.0f &&
isWithin(origin.x + velocity.x * t + 0.5f * acceleration.x * t * t, corner.x, scale.x) &&
isWithin(origin.y + velocity.y * t + 0.5f * acceleration.y * t * t, corner.y, scale.y)) {
minDistance = t;
}
}
// Intersect with the plane z = 0 and make sure the intersection is within dimensions
bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec2& dimensions, float& parabolicDistance) {
glm::vec2 localCorner = -0.5f * dimensions;
float minDistance = FLT_MAX;
if (fabsf(acceleration.z) < EPSILON) {
if (fabsf(velocity.z) > EPSILON) {
// Handle the degenerate case where we only have a line in the z-axis
float possibleDistance = -origin.z / velocity.z;
checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions);
}
} else {
float a = 0.5f * acceleration.z;
float b = velocity.z;
float c = origin.z;
glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int i = 0; i < 2; i++) {
checkPossibleParabolicIntersectionWithZPlane(possibleDistances[i], minDistance, origin, velocity, acceleration, localCorner, dimensions);
}
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
return true;
}
return false;
}
bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& center, float radius, float& parabolicDistance) {
glm::vec3 localCenter = center - origin;
float radiusSquared = radius * radius;
float accelerationLength = glm::length(acceleration);
float minDistance = FLT_MAX;
if (accelerationLength < EPSILON) {
// Handle the degenerate case where acceleration == (0, 0, 0)
glm::vec3 offset = origin - center;
float a = glm::dot(velocity, velocity);
float b = 2.0f * glm::dot(velocity, offset);
float c = glm::dot(offset, offset) - radius * radius;
glm::vec2 possibleDistances(FLT_MAX);
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int i = 0; i < 2; i++) {
if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) {
minDistance = possibleDistances[i];
}
}
}
} else {
glm::vec3 vectorOnPlane = velocity;
if (fabsf(glm::dot(glm::normalize(velocity), glm::normalize(acceleration))) > 1.0f - EPSILON) {
// Handle the degenerate case where velocity is parallel to acceleration
// We pick t = 1 and calculate a second point on the plane
vectorOnPlane = velocity + 0.5f * acceleration;
}
// Get the normal of the plane, the cross product of two vectors on the plane
glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration));
// Project vector from plane to sphere center onto the normal
float distance = glm::dot(localCenter, normal);
// Exit early if the sphere doesn't intersect the plane defined by the parabola
if (fabsf(distance) > radius) {
return false;
}
glm::vec3 circleCenter = center - distance * normal;
float circleRadius = sqrtf(radiusSquared - distance * distance);
glm::vec3 q = glm::normalize(acceleration);
glm::vec3 p = glm::cross(normal, q);
float a1 = accelerationLength * 0.5f;
float b1 = glm::dot(velocity, q);
float c1 = glm::dot(origin - circleCenter, q);
float a2 = glm::dot(velocity, p);
float b2 = glm::dot(origin - circleCenter, p);
float a = a1 * a1;
float b = 2.0f * a1 * b1;
float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2;
float d = 2.0f * b1 * c1 + 2.0f * a2 * b2;
float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius;
glm::vec4 possibleDistances(FLT_MAX);
if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) {
for (int i = 0; i < 4; i++) {
if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) {
minDistance = possibleDistances[i];
}
}
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
return true;
}
return false;
}
void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface) {
// Check if we're hitting the backface in the rotated coordinate space
float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * t;
if (!allowBackface && localIntersectionVelocityZ < 0.0f) {
return;
}
// Check that the point is within all three sides
glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t;
if (t < minDistance && t > 0.0f &&
glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f &&
glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f &&
glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) {
minDistance = t;
}
}
bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface) {
glm::vec3 normal = glm::normalize(glm::cross(v2 - v1, v0 - v1));
// We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin
glm::quat inverseRot;
// Note: OpenGL view matrix is already the inverse of our camera matrix
// if the direction is nearly aligned with the Y axis, then use the X axis for 'up'
const float MAX_ABS_Y_COMPONENT = 0.9999991f;
if (fabsf(normal.y) > MAX_ABS_Y_COMPONENT) {
inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_X));
} else {
inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_Y));
}
glm::vec3 localOrigin = inverseRot * (origin - v0);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
float minDistance = FLT_MAX;
if (fabsf(localAcceleration.z) < EPSILON) {
if (fabsf(localVelocity.z) > EPSILON) {
float possibleDistance = -localOrigin.z / localVelocity.z;
checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration,
localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface);
}
} else {
float a = 0.5f * localAcceleration.z;
float b = localVelocity.z;
float c = localOrigin.z;
glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int i = 0; i < 2; i++) {
checkPossibleParabolicIntersectionWithTriangle(possibleDistances[i], minDistance, origin, velocity, acceleration,
localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface);
}
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
return true;
}
return false;
}
bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance) {
if (start == end) {
return findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, parabolicDistance); // handle degenerate case
}
if (glm::distance2(origin, start) < radius * radius) { // inside start sphere
float startDistance;
bool intersectsStart = findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, startDistance);
if (glm::distance2(origin, end) < radius * radius) { // also inside end sphere
float endDistance;
bool intersectsEnd = findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, endDistance);
if (endDistance < startDistance) {
parabolicDistance = endDistance;
return intersectsEnd;
}
}
parabolicDistance = startDistance;
return intersectsStart;
} else if (glm::distance2(origin, end) < radius * radius) { // inside end sphere (and not start sphere)
return findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, parabolicDistance);
}
// We are either inside the middle of the capsule or outside it completely
// Either way, we need to check all three parts of the capsule and find the closest intersection
glm::vec3 results(FLT_MAX);
findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, results[0]);
findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, results[1]);
// We rotate the infinite cylinder to be aligned with the y-axis and then cap the values at the end
glm::quat inverseRot = glm::inverse(rotation);
glm::vec3 localOrigin = inverseRot * (origin - start);
glm::vec3 localVelocity = inverseRot * velocity;
glm::vec3 localAcceleration = inverseRot * acceleration;
float capsuleLength = glm::length(end - start);
const float MIN_ACCELERATION_PRODUCT = 0.00001f;
if (fabsf(localAcceleration.x * localAcceleration.z) < MIN_ACCELERATION_PRODUCT) {
// Handle the degenerate case where we only have a line in the XZ plane
float a = localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z;
float b = 2.0f * (localVelocity.x * localOrigin.x + localVelocity.z * localOrigin.z);
float c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius;
glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int i = 0; i < 2; i++) {
if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) {
float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i];
if (y > 0.0f && y < capsuleLength) {
results[2] = possibleDistances[i];
}
}
}
}
} else {
float a = 0.25f * (localAcceleration.x * localAcceleration.x + localAcceleration.z * localAcceleration.z);
float b = localVelocity.x * localAcceleration.x + localVelocity.z * localAcceleration.z;
float c = localOrigin.x * localAcceleration.x + localOrigin.z * localAcceleration.z + localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z;
float d = 2.0f * (localOrigin.x * localVelocity.x + localOrigin.z * localVelocity.z);
float e = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius;
glm::vec4 possibleDistances(FLT_MAX);
if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) {
for (int i = 0; i < 4; i++) {
if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) {
float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i];
if (y > 0.0f && y < capsuleLength) {
results[2] = possibleDistances[i];
}
}
}
}
}
float minDistance = FLT_MAX;
for (int i = 0; i < 3; i++) {
minDistance = glm::min(minDistance, results[i]);
}
parabolicDistance = minDistance;
return minDistance != FLT_MAX;
}
void checkPossibleParabolicIntersection(float t, int i, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& corner, const glm::vec3& scale, bool& hit) {
if (t < minDistance && t > 0.0f &&
isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, corner[(i + 1) % 3], scale[(i + 1) % 3]) &&
isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, corner[(i + 2) % 3], scale[(i + 2) % 3])) {
minDistance = t;
hit = true;
}
}
inline float parabolaVelocityAtT(float velocity, float acceleration, float t) {
return velocity + acceleration * t;
}
bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) {
float minDistance = FLT_MAX;
BoxFace minFace = UNKNOWN_FACE;
glm::vec3 minNormal;
glm::vec2 possibleDistances;
float a, b, c;
// Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance
// that is within the bounds of the other two dimensions
for (int i = 0; i < 3; i++) {
if (fabsf(acceleration[i]) < EPSILON) {
// Handle the degenerate case where we only have a line in this axis
if (origin[i] < corner[i]) {
{ // min
if (velocity[i] > 0.0f) {
float possibleDistance = (corner[i] - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
} else if (origin[i] > corner[i] + scale[i]) {
{ // max
if (velocity[i] < 0.0f) {
float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
} else {
{ // min
if (velocity[i] < 0.0f) {
float possibleDistance = (corner[i] - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
{ // max
if (velocity[i] > 0.0f) {
float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else {
a = 0.5f * acceleration[i];
b = velocity[i];
if (origin[i] < corner[i]) {
// If we're below corner, we have the following cases:
// - within bounds on other axes
// - if +velocity or +acceleration
// - can only hit MIN_FACE with -normal
// - else
// - if +acceleration
// - can only hit MIN_FACE with -normal
// - else if +velocity
// - can hit MIN_FACE with -normal iff velocity at intersection is +
// - else can hit MAX_FACE with +normal iff velocity at intersection is -
if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] &&
origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) {
if (velocity[i] > 0.0f || acceleration[i] > 0.0f) {
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else {
if (acceleration[i] > 0.0f) {
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
} else if (velocity[i] > 0.0f) {
bool hit = false;
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
if (!hit) { // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
}
}
} else if (origin[i] > corner[i] + scale[i]) {
// If we're above corner + scale, we have the following cases:
// - within bounds on other axes
// - if -velocity or -acceleration
// - can only hit MAX_FACE with +normal
// - else
// - if -acceleration
// - can only hit MAX_FACE with +normal
// - else if -velocity
// - can hit MAX_FACE with +normal iff velocity at intersection is -
// - else can hit MIN_FACE with -normal iff velocity at intersection is +
if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] &&
origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) {
if (velocity[i] < 0.0f || acceleration[i] < 0.0f) {
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
}
} else {
if (acceleration[i] < 0.0f) {
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
} else if (velocity[i] < 0.0f) {
bool hit = false;
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
if (!hit) { // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
}
} else {
// If we're between corner and corner + scale, we have the following cases:
// - within bounds on other axes
// - if -velocity and -acceleration
// - can only hit MIN_FACE with +normal
// - else if +velocity and +acceleration
// - can only hit MAX_FACE with -normal
// - else
// - can hit MIN_FACE with +normal iff velocity at intersection is -
// - can hit MAX_FACE with -normal iff velocity at intersection is +
// - else
// - if -velocity and +acceleration
// - can hit MIN_FACE with -normal iff velocity at intersection is +
// - else if +velocity and -acceleration
// - can hit MAX_FACE with +normal iff velocity at intersection is -
if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] &&
origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) {
if (velocity[i] < 0.0f && acceleration[i] < 0.0f) {
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
} else if (velocity[i] > 0.0f && acceleration[i] > 0.0f) {
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
} else {
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else {
if (velocity[i] < 0.0f && acceleration[i] > 0.0f) {
{ // min
c = origin[i] - corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
} else if (velocity[i] > 0.0f && acceleration[i] < 0.0f) {
{ // max
c = origin[i] - (corner[i] + scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
for (int j = 0; j < 2; j++) {
if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) {
checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit);
}
}
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
}
}
}
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
face = minFace;
surfaceNormal = minNormal;
return true;
}
return false;
}
void swingTwistDecomposition(const glm::quat& rotation,
const glm::vec3& direction,
glm::quat& swing,
@ -941,3 +1684,142 @@ void generateBoundryLinesForDop14(const std::vector<float>& dots, const glm::vec
}
}
}
bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) {
float discriminant = b * b - 4.0f * a * c;
if (discriminant < 0.0f) {
return false;
} else if (discriminant == 0.0f) {
roots.x = (-b + sqrtf(discriminant)) / (2.0f * a);
} else {
float discriminantRoot = sqrtf(discriminant);
roots.x = (-b + discriminantRoot) / (2.0f * a);
roots.y = (-b - discriminantRoot) / (2.0f * a);
}
return true;
}
// The following functions provide an analytical solution to a quartic equation, adapted from the solution here: https://github.com/sasamil/Quartic
unsigned int solveP3(float* x, float a, float b, float c) {
float a2 = a * a;
float q = (a2 - 3.0f * b) / 9.0f;
float r = (a * (2.0f * a2 - 9.0f * b) + 27.0f * c) / 54.0f;
float r2 = r * r;
float q3 = q * q * q;
float A, B;
if (r2 < q3) {
float t = r / sqrtf(q3);
t = glm::clamp(t, -1.0f, 1.0f);
t = acosf(t);
a /= 3.0f;
q = -2.0f * sqrtf(q);
x[0] = q * cosf(t / 3.0f) - a;
x[1] = q * cosf((t + 2.0f * (float)M_PI) / 3.0f) - a;
x[2] = q * cosf((t - 2.0f * (float)M_PI) / 3.0f) - a;
return 3;
} else {
A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f);
if (r < 0) {
A = -A;
}
B = (A == 0.0f ? 0.0f : q / A);
a /= 3.0f;
x[0] = (A + B) - a;
x[1] = -0.5f * (A + B) - a;
x[2] = 0.5f * sqrtf(3.0f) * (A - B);
if (fabsf(x[2]) < EPSILON) {
x[2] = x[1];
return 2;
}
return 1;
}
}
bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) {
float a3 = -b;
float b3 = a * c - 4.0f *d;
float c3 = -a * a * d - c * c + 4.0f * b * d;
float px3[3];
unsigned int iZeroes = solveP3(px3, a3, b3, c3);
float q1, q2, p1, p2, D, sqD, y;
y = px3[0];
if (iZeroes != 1) {
if (fabsf(px3[1]) > fabsf(y)) {
y = px3[1];
}
if (fabsf(px3[2]) > fabsf(y)) {
y = px3[2];
}
}
D = y * y - 4.0f * d;
if (fabsf(D) < EPSILON) {
q1 = q2 = 0.5f * y;
D = a * a - 4.0f * (b - y);
if (fabsf(D) < EPSILON) {
p1 = p2 = 0.5f * a;
} else {
sqD = sqrtf(D);
p1 = 0.5f * (a + sqD);
p2 = 0.5f * (a - sqD);
}
} else {
sqD = sqrtf(D);
q1 = 0.5f * (y + sqD);
q2 = 0.5f * (y - sqD);
p1 = (a * q1 - c) / (q1 - q2);
p2 = (c - a * q2) / (q1 - q2);
}
std::complex<float> x1, x2, x3, x4;
D = p1 * p1 - 4.0f * q1;
if (D < 0.0f) {
x1.real(-0.5f * p1);
x1.imag(0.5f * sqrtf(-D));
x2 = std::conj(x1);
} else {
sqD = sqrtf(D);
x1.real(0.5f * (-p1 + sqD));
x2.real(0.5f * (-p1 - sqD));
}
D = p2 * p2 - 4.0f * q2;
if (D < 0.0f) {
x3.real(-0.5f * p2);
x3.imag(0.5f * sqrtf(-D));
x4 = std::conj(x3);
} else {
sqD = sqrtf(D);
x3.real(0.5f * (-p2 + sqD));
x4.real(0.5f * (-p2 - sqD));
}
bool hasRealRoot = false;
if (fabsf(x1.imag()) < EPSILON) {
roots.x = x1.real();
hasRealRoot = true;
}
if (fabsf(x2.imag()) < EPSILON) {
roots.y = x2.real();
hasRealRoot = true;
}
if (fabsf(x3.imag()) < EPSILON) {
roots.z = x3.real();
hasRealRoot = true;
}
if (fabsf(x4.imag()) < EPSILON) {
roots.w = x4.real();
hasRealRoot = true;
}
return hasRealRoot;
}
bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) {
return solve_quartic(b / a, c / a, d / a, e / a, roots);
}

View file

@ -14,6 +14,7 @@
#include <glm/glm.hpp>
#include <vector>
#include "BoxBase.h"
class Plane;
@ -73,6 +74,11 @@ bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3&
glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration);
bool findIntersection(float origin, float direction, float corner, float size, float& distance);
bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance);
bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& corner, const glm::vec3& scale, float& distance,
BoxFace& face, glm::vec3& surfaceNormal);
bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& center, float radius, float& distance);
@ -88,6 +94,21 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false);
bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec2& dimensions, float& parabolicDistance);
bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& center, float radius, float& distance);
bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface = false);
bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance);
bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal);
/// \brief decomposes rotation into its components such that: rotation = swing * twist
/// \param rotation[in] rotation to decompose
/// \param direction[in] normalized axis about which the twist happens (typically original direction before rotation applied)
@ -112,6 +133,11 @@ inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3
return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface);
}
inline bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, const Triangle& triangle, float& parabolicDistance, bool allowBackface = false) {
return findParabolaTriangleIntersection(origin, velocity, acceleration, triangle.v0, triangle.v1, triangle.v2, parabolicDistance, allowBackface);
}
int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle* clippedTriangles, int maxClippedTriangleCount);
int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount);
@ -178,4 +204,20 @@ bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& pla
void generateBoundryLinesForDop14(const std::vector<float>& dots, const glm::vec3& center, std::vector<glm::vec3>& linesOut);
bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots);
unsigned int solveP3(float *x, float a, float b, float c);
bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots);
bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots);
bool isWithin(float value, float corner, float size);
bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale);
void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale);
void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal,
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface);
#endif // hifi_GeometryUtil_h

View file

@ -219,6 +219,37 @@ public:
}
};
/**jsdoc
* A PickParabola defines a parabola with a starting point, intitial velocity, and acceleration.
*
* @typedef {object} PickParabola
* @property {Vec3} origin - The starting position of the PickParabola.
* @property {Vec3} velocity - The starting velocity of the parabola.
* @property {Vec3} acceleration - The acceleration that the parabola experiences.
*/
class PickParabola : public MathPick {
public:
PickParabola() : origin(NAN), velocity(NAN), acceleration(NAN) { }
PickParabola(const QVariantMap& pickVariant) : origin(vec3FromVariant(pickVariant["origin"])), velocity(vec3FromVariant(pickVariant["velocity"])), acceleration(vec3FromVariant(pickVariant["acceleration"])) {}
PickParabola(const glm::vec3& origin, const glm::vec3 velocity, const glm::vec3 acceleration) : origin(origin), velocity(velocity), acceleration(acceleration) {}
glm::vec3 origin;
glm::vec3 velocity;
glm::vec3 acceleration;
operator bool() const override {
return !(glm::any(glm::isnan(origin)) || glm::any(glm::isnan(velocity)) || glm::any(glm::isnan(acceleration)));
}
bool operator==(const PickParabola& other) const {
return (origin == other.origin && velocity == other.velocity && acceleration == other.acceleration);
}
QVariantMap toVariantMap() const override {
QVariantMap pickParabola;
pickParabola["origin"] = vec3toVariant(origin);
pickParabola["velocity"] = vec3toVariant(velocity);
pickParabola["acceleration"] = vec3toVariant(acceleration);
return pickParabola;
}
};
namespace std {
inline void hash_combine(std::size_t& seed) { }
@ -273,6 +304,15 @@ namespace std {
}
};
template <>
struct hash<PickParabola> {
size_t operator()(const PickParabola& a) const {
size_t result = 0;
hash_combine(result, a.origin, a.velocity, a.acceleration);
return result;
}
};
template <>
struct hash<QString> {
size_t operator()(const QString& a) const {

View file

@ -51,6 +51,26 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3&
return result;
}
bool TriangleSet::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, Triangle& triangle, bool precision, bool allowBackface) {
// reset our distance to be the max possible, lower level tests will store best distance here
parabolicDistance = FLT_MAX;
if (!_isBalanced) {
balanceOctree();
}
int trianglesTouched = 0;
auto result = _triangleOctree.findParabolaIntersection(origin, velocity, acceleration, parabolicDistance, face, triangle, precision, trianglesTouched, allowBackface);
#if WANT_DEBUGGING
if (precision) {
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size();
}
#endif
return result;
}
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
if (!_bounds.contains(point)) {
return false;
@ -95,40 +115,63 @@ void TriangleSet::balanceOctree() {
// Determine of the given ray (origin/direction) in model space intersects with any triangles
// in the set. If an intersection occurs, the distance and surface normal will be provided.
bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched, bool allowBackface) {
float& distance, BoxFace& face, Triangle& triangle, bool precision,
int& trianglesTouched, bool allowBackface) {
bool intersectedSomething = false;
float boxDistance = distance;
float bestDistance = distance;
glm::vec3 surfaceNormal;
float bestDistance = FLT_MAX;
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
// if our bounding box intersects at a distance greater than the current known
// best distance, and our origin isn't inside the boounds, then we can safely
// not check any of our triangles
if (boxDistance > bestDistance && !_bounds.contains(origin)) {
return false;
}
if (precision) {
for (const auto& triangleIndex : _triangleIndices) {
const auto& thisTriangle = _allTriangles[triangleIndex];
float thisTriangleDistance;
trianglesTouched++;
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
if (thisTriangleDistance < bestDistance) {
bestDistance = thisTriangleDistance;
intersectedSomething = true;
triangle = thisTriangle;
distance = bestDistance;
}
if (precision) {
for (const auto& triangleIndex : _triangleIndices) {
const auto& thisTriangle = _allTriangles[triangleIndex];
float thisTriangleDistance;
trianglesTouched++;
if (findRayTriangleIntersection(origin, direction, thisTriangle, thisTriangleDistance, allowBackface)) {
if (thisTriangleDistance < bestDistance) {
bestDistance = thisTriangleDistance;
intersectedSomething = true;
triangle = thisTriangle;
}
}
} else {
intersectedSomething = true;
distance = boxDistance;
}
} else {
intersectedSomething = true;
bestDistance = distance;
}
if (intersectedSomething) {
distance = bestDistance;
}
return intersectedSomething;
}
bool TriangleSet::TriangleOctreeCell::findParabolaIntersectionInternal(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, Triangle& triangle, bool precision,
int& trianglesTouched, bool allowBackface) {
bool intersectedSomething = false;
float bestDistance = FLT_MAX;
if (precision) {
for (const auto& triangleIndex : _triangleIndices) {
const auto& thisTriangle = _allTriangles[triangleIndex];
float thisTriangleDistance;
trianglesTouched++;
if (findParabolaTriangleIntersection(origin, velocity, acceleration, thisTriangle, thisTriangleDistance, allowBackface)) {
if (thisTriangleDistance < bestDistance) {
bestDistance = thisTriangleDistance;
intersectedSomething = true;
triangle = thisTriangle;
}
}
}
} else {
intersectedSomething = true;
bestDistance = parabolicDistance;
}
if (intersectedSomething) {
parabolicDistance = bestDistance;
}
return intersectedSomething;
@ -204,45 +247,42 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
_triangleIndices.push_back(triangleIndex);
}
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
float& distance, BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
bool allowBackface) {
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, Triangle& triangle, bool precision, int& trianglesTouched,
bool allowBackface) {
if (_population < 1) {
return false; // no triangles below here, so we can't intersect
}
float bestLocalDistance = distance;
float bestLocalDistance = FLT_MAX;
BoxFace bestLocalFace;
Triangle bestLocalTriangle;
glm::vec3 bestLocalNormal;
bool intersects = false;
// if the ray intersects our bounding box, then continue
if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) {
float boxDistance = FLT_MAX;
// if the pick intersects our bounding box, then continue
if (getBounds().findRayIntersection(origin, direction, boxDistance, bestLocalFace, bestLocalNormal)) {
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
// then we know that none of our triangles can represent a better intersection and we can return
if (bestLocalDistance > distance) {
if (boxDistance > distance) {
return false;
}
bestLocalDistance = distance;
float childDistance = distance;
BoxFace childFace;
Triangle childTriangle;
// if we're not yet at the max depth, then check which child the triangle fits in
if (_depth < MAX_DEPTH) {
float bestChildDistance = FLT_MAX;
for (auto& child : _children) {
// check each child, if there's an intersection, it will return some distance that we need
// to compare against the other results, because there might be multiple intersections and
// we will always choose the best (shortest) intersection
float childDistance = bestChildDistance;
BoxFace childFace;
Triangle childTriangle;
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
if (childDistance < bestLocalDistance) {
bestLocalDistance = childDistance;
bestChildDistance = childDistance;
bestLocalFace = childFace;
bestLocalTriangle = childTriangle;
intersects = true;
@ -251,11 +291,14 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
}
}
// also check our local triangle set
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childTriangle, precision, trianglesTouched, allowBackface)) {
if (childDistance < bestLocalDistance) {
bestLocalDistance = childDistance;
bestLocalFace = childFace;
bestLocalTriangle = childTriangle;
float internalDistance = boxDistance;
BoxFace internalFace;
Triangle internalTriangle;
if (findRayIntersectionInternal(origin, direction, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
if (internalDistance < bestLocalDistance) {
bestLocalDistance = internalDistance;
bestLocalFace = internalFace;
bestLocalTriangle = internalTriangle;
intersects = true;
}
}
@ -267,3 +310,68 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
}
return intersects;
}
bool TriangleSet::TriangleOctreeCell::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity,
const glm::vec3& acceleration, float& parabolicDistance,
BoxFace& face, Triangle& triangle, bool precision,
int& trianglesTouched, bool allowBackface) {
if (_population < 1) {
return false; // no triangles below here, so we can't intersect
}
float bestLocalDistance = FLT_MAX;
BoxFace bestLocalFace;
Triangle bestLocalTriangle;
glm::vec3 bestLocalNormal;
bool intersects = false;
float boxDistance = FLT_MAX;
// if the pick intersects our bounding box, then continue
if (getBounds().findParabolaIntersection(origin, velocity, acceleration, boxDistance, bestLocalFace, bestLocalNormal)) {
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
// then we know that none of our triangles can represent a better intersection and we can return
if (boxDistance > parabolicDistance) {
return false;
}
// if we're not yet at the max depth, then check which child the triangle fits in
if (_depth < MAX_DEPTH) {
float bestChildDistance = FLT_MAX;
for (auto& child : _children) {
// check each child, if there's an intersection, it will return some distance that we need
// to compare against the other results, because there might be multiple intersections and
// we will always choose the best (shortest) intersection
float childDistance = bestChildDistance;
BoxFace childFace;
Triangle childTriangle;
if (child.second.findParabolaIntersection(origin, velocity, acceleration, childDistance, childFace, childTriangle, precision, trianglesTouched)) {
if (childDistance < bestLocalDistance) {
bestLocalDistance = childDistance;
bestChildDistance = childDistance;
bestLocalFace = childFace;
bestLocalTriangle = childTriangle;
intersects = true;
}
}
}
}
// also check our local triangle set
float internalDistance = boxDistance;
BoxFace internalFace;
Triangle internalTriangle;
if (findParabolaIntersectionInternal(origin, velocity, acceleration, internalDistance, internalFace, internalTriangle, precision, trianglesTouched, allowBackface)) {
if (internalDistance < bestLocalDistance) {
bestLocalDistance = internalDistance;
bestLocalFace = internalFace;
bestLocalTriangle = internalTriangle;
intersects = true;
}
}
}
if (intersects) {
parabolicDistance = bestLocalDistance;
face = bestLocalFace;
triangle = bestLocalTriangle;
}
return intersects;
}

Some files were not shown because too many files have changed in this diff Show more