parabola picks, started implementing math

This commit is contained in:
SamGondelman 2018-07-03 09:47:31 -07:00
parent 1f4346d221
commit 90091d11e4
50 changed files with 1057 additions and 60 deletions

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 rotateWithAvatar, PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateWithAvatar, 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 = rot * glm::normalize(_dirOffset);
return PickParabola(pos, _speed * dir, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,31 @@
//
// 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 rotateWithAvatar, 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

@ -19,12 +19,13 @@
#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) :
const PointerTriggers& triggers, bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled) :
Pointer(DependencyManager::get<PickScriptingInterface>()->createRayPick(rayProps), enabled, hover),
_triggers(triggers),
_renderStates(renderStates),
_defaultRenderStates(defaultRenderStates),
_faceAvatar(faceAvatar),
_followNormal(followNormal),
_centerEndY(centerEndY),
_lockEnd(lockEnd),
_distanceScaleEnd(distanceScaleEnd),
@ -78,6 +79,10 @@ void LaserPointer::editRenderState(const std::string& state, const QVariant& sta
if (lineWidth.isValid()) {
_renderStates[state].setLineWidth(lineWidth.toFloat());
}
QVariant rotation = pathProps.toMap()["rotation"];
if (rotation.isValid()) {
_renderStates[state].setEndRot(quatFromVariant(rotation));
}
});
}
@ -148,7 +153,7 @@ void LaserPointer::updateRenderStateOverlay(const OverlayID& id, const QVariant&
}
}
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const QUuid& objectID, const PickRay& pickRay) {
void LaserPointer::updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay) {
if (!renderState.getStartID().isNull()) {
QVariantMap startProps;
startProps.insert("position", vec3toVariant(pickRay.origin));
@ -182,10 +187,11 @@ void LaserPointer::updateRenderState(const RenderState& renderState, const Inter
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)));
endProps.insert("position", vec3toVariant(endVec + currentUpVector * vec3(0.0f, 0.5f * dim.y, 0.0f)));
}
if (_faceAvatar) {
endProps.insert("rotation", quatToVariant(faceAvatarRotation));
glm::quat rotation = _faceAvatar ? faceAvatarRotation : renderState.getEndRot();
endProps.insert("rotation", quatToVariant(rotation));
}
endProps.insert("visible", true);
endProps.insert("ignoreRayIntersection", renderState.doesEndIgnoreRays());
@ -223,12 +229,12 @@ void LaserPointer::updateVisuals(const PickResultPointer& pickResult) {
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);
updateRenderState(_renderStates[_currentRenderState], type, distance, rayPickResult->surfaceNormal, uid, pickRay);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
} else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) {
disableRenderState(_renderStates[_currentRenderState]);
PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay();
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay);
updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, Vectors::UP, QUuid(), pickRay);
} else if (!_currentRenderState.empty()) {
disableRenderState(_renderStates[_currentRenderState]);
disableRenderState(_defaultRenderStates[_currentRenderState].second);
@ -306,6 +312,7 @@ RenderState::RenderState(const OverlayID& startID, const OverlayID& pathID, cons
}
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();
}
}

View file

@ -41,6 +41,9 @@ public:
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; }
void setLineWidth(const float& lineWidth) { _lineWidth = lineWidth; }
const float& getLineWidth() const { return _lineWidth; }
@ -55,6 +58,7 @@ private:
bool _endIgnoreRays;
glm::vec3 _endDim;
glm::quat _endRot;
float _lineWidth;
};
@ -65,7 +69,7 @@ public:
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);
bool faceAvatar, bool followNormal, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool scaleWithAvatar, bool enabled);
~LaserPointer();
void setRenderState(const std::string& state) override;
@ -96,6 +100,7 @@ private:
RenderStateMap _renderStates;
DefaultRenderStateMap _defaultRenderStates;
bool _faceAvatar;
bool _followNormal;
bool _centerEndY;
bool _lockEnd;
bool _distanceScaleEnd;
@ -103,7 +108,7 @@ private:
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 updateRenderState(const RenderState& renderState, const IntersectionType type, float distance, const glm::vec3& normal, const QUuid& objectID, const PickRay& pickRay);
void disableRenderState(const RenderState& renderState);
struct TriggerState {

View file

@ -0,0 +1,27 @@
//
// 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 rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateWithAvatar, 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, _speed * pickRay.direction, getAcceleration());
}
return PickParabola();
}

View file

@ -0,0 +1,23 @@
//
// 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 rotateWithAvatar, 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,57 @@
//
// 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) {
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);
} else {
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
}
}
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
/*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);
} else {*/
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
//}
}
PickResultPointer ParabolaPick::getAvatarIntersection(const PickParabola& pick) {
/*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, glm::vec3(NAN), avatarRes.extraInfo);
} else {*/
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
//}
}
PickResultPointer ParabolaPick::getHUDIntersection(const PickParabola& pick) {
return std::make_shared<ParabolaPickResult>(pick.toVariantMap());
//glm::vec3 hudRes = DependencyManager::get<HMDScriptingInterface>()->calculateParabolaUICollisionPoint(pick);
//return std::make_shared<ParabolaPickResult>(IntersectionType::HUD, QUuid(), glm::distance(pick.origin, hudRes), hudRes, pick);
}
glm::vec3 ParabolaPick::getAcceleration() const {
// TODO: use rotateWithAvatar
return _accelerationAxis;
}

View file

@ -0,0 +1,94 @@
//
// 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()), type(type), intersects(type != NONE), objectID(objectID), distance(distance), parabolicDistance(parabolicDistance), intersection(intersection), surfaceNormal(surfaceNormal), extraInfo(extraInfo) {
}
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;
}
IntersectionType type { NONE };
bool intersects { false };
QUuid objectID;
float distance { FLT_MAX };
float parabolicDistance { FLT_MAX };
glm::vec3 intersection { NAN };
glm::vec3 surfaceNormal { NAN };
QVariantMap extraInfo;
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 distance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newParabolaRes = std::static_pointer_cast<ParabolaPickResult>(newRes);
if (newParabolaRes->distance < distance) {
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 rotateWithAvatar, const PickFilter& filter, float maxDistance, bool enabled) :
Pick(filter, maxDistance, enabled), _speed(speed), _accelerationAxis(accelerationAxis), _rotateWithAvatar(rotateWithAvatar) {}
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 _rotateWithAvatar;
glm::vec3 getAcceleration() const;
};
#endif // hifi_ParabolaPick_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,91 @@ 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} [rotateWithAvatar=true] Whether or not the acceleration axis should rotate with your avatar's local Y axis.
*/
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 rotateWithAvatar = true;
if (propMap["rotateWithAvatar"].isValid()) {
rotateWithAvatar = propMap["rotateWithAvatar"].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, rotateWithAvatar, filter, maxDistance, enabled));
} else {
return DependencyManager::get<PickManager>()->addPick(PickQuery::Parabola, std::make_shared<MouseParabolaPick>(speed, accelerationAxis, rotateWithAvatar, 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, rotateWithAvatar, 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

@ -134,6 +134,11 @@ unsigned int PointerScriptingInterface::createLaserPointer(const QVariant& prope
scaleWithAvatar = propertyMap["scaleWithAvatar"].toBool();
}
bool followNormal = true;
if (propertyMap["followNormal"].isValid()) {
followNormal = propertyMap["followNormal"].toBool();
}
bool enabled = false;
if (propertyMap["enabled"].isValid()) {
enabled = propertyMap["enabled"].toBool();
@ -192,7 +197,8 @@ 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, centerEndY, lockEnd, distanceScaleEnd,
scaleWithAvatar, enabled));
}
void PointerScriptingInterface::editRenderState(unsigned int uid, const QString& renderState, const QVariant& properties) const {

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 rotateWithAvatar,
const PickFilter& filter, float maxDistance, bool enabled) :
ParabolaPick(speed, accelerationAxis, rotateWithAvatar, filter, maxDistance, enabled),
_position(position), _velocity(speed * direction)
{
}
PickParabola StaticParabolaPick::getMathematicalPick() const {
return PickParabola(_position, _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 rotateWithAvatar,
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

@ -65,13 +65,22 @@ public:
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 };
float parabolicDistance { 0 };
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.

View file

@ -1525,19 +1525,26 @@ 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 };
glm::vec3 intersection;
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 };
glm::vec3 intersection;
QVariantMap extraInfo;
};
Q_DECLARE_METATYPE(AvatarEntityMap)
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value);

View file

@ -278,7 +278,7 @@ EntityItemProperties RenderableModelEntityItem::getProperties(EntityPropertyFlag
return properties;
}
bool RenderableModelEntityItem::supportsDetailedRayIntersection() const {
bool RenderableModelEntityItem::supportsDetailedIntersection() const {
return true;
}

View file

@ -66,7 +66,7 @@ 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,

View file

@ -51,7 +51,7 @@ 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,

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,15 +1049,6 @@ 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__);

View file

@ -71,11 +71,10 @@ 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;
@ -87,6 +86,18 @@ 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 +142,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 +1912,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
}
@ -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,7 @@ 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));
entityID = entity->getEntityItemID();
}
}
@ -287,6 +287,134 @@ 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;
EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, element, distanceToElementDetails,
face, 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,
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)) {
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));
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& 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

@ -314,7 +314,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 { 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 { 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;
}
@ -282,6 +282,14 @@ 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 {
// TODO
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

@ -140,6 +140,14 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
return findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance);
}
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 {
// TODO
return false;
}
void TextEntityItem::setText(const QString& value) {
withWriteLock([&] {
_text = 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

@ -121,6 +121,14 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
}
}
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 {
// TODO
return false;
}
void WebEntityItem::setSourceUrl(const QString& value) {
withWriteLock([&] {
if (_sourceUrl != value) {

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,12 +59,13 @@ 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;
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };

View file

@ -287,6 +287,68 @@ bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct
return false;
}
void AABox::checkPossibleParabolicIntersection(float t, int i, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const {
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;
}
}
bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
float minDistance = FLT_MAX;
BoxFace minFace;
glm::vec3 minNormal;
std::pair<float, float> 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++) {
a = 0.5f * acceleration[i];
b = velocity[i];
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, 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;
checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, 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;
}
bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const {
glm::vec3 localCenter = calcCenter() - origin;
float distance = glm::dot(localCenter, direction);
@ -296,6 +358,28 @@ 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 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;
}
// Get the normal of the plane, the cross product of two vectors on the plane
// Assumes: velocity and acceleration != 0 and normalize(velocity) != normalize(acceleration)
glm::vec3 normal = glm::normalize(glm::cross(velocity, acceleration));
// 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;
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

@ -283,6 +283,68 @@ bool AACube::findRayIntersection(const glm::vec3& origin, const glm::vec3& direc
return false;
}
void AACube::checkPossibleParabolicIntersection(float t, int i, float& minDistance,
const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, bool& hit) const {
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) &&
isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, _corner[(i + 2) % 3], _scale)) {
minDistance = t;
hit = true;
}
}
bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
float minDistance = FLT_MAX;
BoxFace minFace;
glm::vec3 minNormal;
std::pair<float, float> 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++) {
a = 0.5f * acceleration[i];
b = velocity[i];
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
{ // max
c = origin[i] - (_corner[i] + _scale);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.first, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.second, i, minDistance, origin, velocity, acceleration, 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;
}
bool AACube::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 - glm::vec3(_scale), Vectors::ZERO);

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;
@ -76,6 +78,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;
float _scale;
};

View file

@ -941,3 +941,17 @@ void generateBoundryLinesForDop14(const std::vector<float>& dots, const glm::vec
}
}
}
bool computeRealQuadraticRoots(float a, float b, float c, std::pair<float, float>& roots) {
float discriminant = b * b - 4.0f * a * c;
if (discriminant < 0.0f) {
return false;
} else if (discriminant == 0.0f) {
roots.first = (-b + sqrtf(discriminant)) / (2.0f * a);
} else {
float discriminantRoot = sqrtf(discriminant);
roots.first = (-b + discriminantRoot) / (2.0f * a);
roots.second = (-b - discriminantRoot) / (2.0f * a);
}
return true;
}

View file

@ -178,4 +178,6 @@ 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, std::pair<float, float>& roots);
#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 {