parabola-overlay and -avatar intersection, handle case where acceleration == 0

This commit is contained in:
SamGondelman 2018-07-17 12:41:03 -07:00
parent ca5ce888f4
commit 845ddda695
41 changed files with 816 additions and 275 deletions

View file

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

@ -573,6 +573,77 @@ 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.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

@ -141,6 +141,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

@ -26,29 +26,29 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick)
}
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
/*ParabolaToOverlayIntersectionResult overlayRes =
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 {*/
} 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>());
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 {*/
} 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);
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);
}
glm::vec3 ParabolaPick::getAcceleration() const {

View file

@ -58,11 +58,11 @@ public:
}
bool doesIntersect() const override { return intersects; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; }
bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return parabolicDistance < maxDistance; }
PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override {
auto newParabolaRes = std::static_pointer_cast<ParabolaPickResult>(newRes);
if (newParabolaRes->distance < distance) {
if (newParabolaRes->parabolicDistance < parabolicDistance) {
return std::make_shared<ParabolaPickResult>(*newParabolaRes);
} else {
return std::make_shared<ParabolaPickResult>(*this);

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,65 @@ 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;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = rotation * Vectors::FRONT;
} else {
face = MAX_Z_FACE;
surfaceNormal = rotation * -Vectors::FRONT;
}
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

@ -193,7 +193,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,54 @@ 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;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = rotation * Vectors::FRONT;
} else {
face = MAX_Z_FACE;
surfaceNormal = rotation * -Vectors::FRONT;
}
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

@ -373,8 +373,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.
@ -510,17 +509,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,6 +573,54 @@ RayToOverlayIntersectionResult Overlays::findRayIntersectionVector(const PickRay
return result;
}
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;
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;
}
}
}
}
return result;
}
QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) {
auto obj = engine->newObject();
obj.setProperty("intersects", value.intersects);
@ -1046,7 +1094,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

@ -119,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,47 @@ 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;
if (localIntersectionVelocityZ > 0.0f) {
face = MIN_Z_FACE;
surfaceNormal = rotation * Vectors::FRONT;
} else {
face = MAX_Z_FACE;
surfaceNormal = rotation * -Vectors::FRONT;
}
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

@ -91,6 +91,23 @@ bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::ve
return _localBoundingBox.findRayIntersection(overlayFrameOrigin, overlayFrameDirection, distance, face, surfaceNormal);
}
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.
return _localBoundingBox.findParabolaIntersection(overlayFrameOrigin, overlayFrameVelocity, overlayFrameAcceleration, parabolicDistance, face, surfaceNormal);
}
Transform Volume3DOverlay::evalRenderTransform() {
Transform transform = getTransform();
#ifndef USE_SN_SCALE

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

@ -539,8 +539,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.
@ -623,20 +622,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

@ -1563,6 +1563,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

@ -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

@ -139,15 +139,14 @@ bool TextEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = rotation * -Vectors::FRONT;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = rotation * Vectors::FRONT;
surfaceNormal = forward;
}
return true;
} else {
return false;
}
return false;
}
bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
@ -174,9 +173,8 @@ bool TextEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c
surfaceNormal = rotation * -Vectors::FRONT;
}
return true;
} else {
return false;
}
return false;
}
void TextEntityItem::setText(const QString& value) {

View file

@ -116,10 +116,10 @@ bool WebEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const g
glm::vec3 forward = rotation * Vectors::FRONT;
if (glm::dot(forward, direction) > 0.0f) {
face = MAX_Z_FACE;
surfaceNormal = rotation * -Vectors::FRONT;
surfaceNormal = -forward;
} else {
face = MIN_Z_FACE;
surfaceNormal = rotation * Vectors::FRONT;
surfaceNormal = forward;
}
return true;
} else {

View file

@ -303,68 +303,123 @@ bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& v
// 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++) {
// TODO: handle case where a is 0
a = 0.5f * acceleration[i];
b = velocity[i];
if (origin[i] < _corner[i]) {
// If we're below _corner, we only need to check the min face
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
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, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else if (origin[i] > _corner[i] + _scale[i]) {
// If we're above _corner + _scale, we only need to check the max face
{ // max
c = origin[i] - (_corner[i] + _scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, 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]) {
{ // 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, 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, 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, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else {
// If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
a = 0.5f * acceleration[i];
b = velocity[i];
if (origin[i] < _corner[i]) {
// If we're below _corner, we only need to check the min face
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, 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.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, 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 only need to check the max face
{ // max
c = origin[i] - (_corner[i] + _scale[i]);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
} else {
// If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
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.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
@ -399,14 +454,29 @@ bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const
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));
float velocityLength2 = glm::length2(velocity);
if (glm::length2(acceleration) < EPSILON) {
if (velocityLength2 < EPSILON) {
// No intersection if velocity == acceleration == (0, 0, 0)
return false;
}
// Handle the degenerate case where acceleration == (0, 0, 0)
return rayHitsBoundingSphere(origin, glm::normalize(velocity));
} else {
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));
// Project vector from plane to sphere center onto the normal
float distance = glm::dot(localCenter, normal);
if (distance * distance < radiusSquared) {
return true;
// Project vector from plane to sphere center onto the normal
float distance = glm::dot(localCenter, normal);
if (distance * distance < radiusSquared) {
return true;
}
}
return false;
}

View file

@ -289,7 +289,7 @@ void AACube::checkPossibleParabolicIntersection(float t, int i, float& minDistan
}
bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration,
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const {
float minDistance = FLT_MAX;
BoxFace minFace;
glm::vec3 minNormal;
@ -299,68 +299,123 @@ bool AACube::findParabolaIntersection(const glm::vec3& origin, const glm::vec3&
// 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++) {
// TODO: handle case where a is 0
a = 0.5f * acceleration[i];
b = velocity[i];
if (origin[i] < _corner[i]) {
// If we're below _corner, we only need to check the min face
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
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, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else if (origin[i] > _corner[i] + _scale) {
// If we're above _corner + _scale, we only need to check the max face
{ // max
c = origin[i] - (_corner[i] + _scale);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
} else if (origin[i] > _corner[i] + _scale) {
{ // max
if (velocity[i] < 0.0f) {
float possibleDistance = (_corner[i] + _scale - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, 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, 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 - origin[i]) / velocity[i];
bool hit = false;
checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}
} else {
// If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
a = 0.5f * acceleration[i];
b = velocity[i];
if (origin[i] < _corner[i]) {
// If we're below _corner, we only need to check the min face
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, 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.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
} else if (origin[i] > _corner[i] + _scale) {
// If we're above _corner + _scale, we only need to check the max face
{ // max
c = origin[i] - (_corner[i] + _scale);
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
minNormal = glm::vec3(0.0f);
minNormal[i] = 1.0f;
}
}
}
} else {
// If we're inside on this axis, we could hit either face depending on our velocity and acceleration, so we need to check both
{ // min
c = origin[i] - _corner[i];
possibleDistances = { FLT_MAX, FLT_MAX };
if (computeRealQuadraticRoots(a, b, c, possibleDistances)) {
bool hit = false;
checkPossibleParabolicIntersection(possibleDistances.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i + 1);
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.x, i, minDistance, origin, velocity, acceleration, hit);
checkPossibleParabolicIntersection(possibleDistances.y, i, minDistance, origin, velocity, acceleration, hit);
if (hit) {
minFace = BoxFace(2 * i);
minNormal = glm::vec3(0.0f);
minNormal[i] = -1.0f;
}
}
}
}

View file

@ -734,13 +734,21 @@ bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3&
glm::vec2 localCorner = -0.5f * dimensions;
float minDistance = FLT_MAX;
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)) {
checkPossibleParabolicIntersectionWithZPlane(possibleDistances.x, minDistance, origin, velocity, acceleration, localCorner, dimensions);
checkPossibleParabolicIntersectionWithZPlane(possibleDistances.y, minDistance, origin, velocity, acceleration, localCorner, dimensions);
if (fabsf(acceleration.z) < EPSILON) {
// Handle the degenerate case where we only have a line in the z-axis
if (fabsf(velocity.z) > EPSILON) {
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)) {
checkPossibleParabolicIntersectionWithZPlane(possibleDistances.x, minDistance, origin, velocity, acceleration, localCorner, dimensions);
checkPossibleParabolicIntersectionWithZPlane(possibleDistances.y, minDistance, origin, velocity, acceleration, localCorner, dimensions);
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
@ -754,43 +762,72 @@ bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& ve
glm::vec3 localCenter = center - origin;
float radiusSquared = radius * radius;
// 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);
// 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 = glm::length(acceleration) * 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;
float velocityLength2 = glm::length2(velocity);
float accelerationLength = glm::length(acceleration);
float minDistance = FLT_MAX;
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 (accelerationLength < EPSILON) {
if (velocityLength2 < EPSILON) {
// No intersection if velocity == acceleration == (0, 0, 0)
return false;
}
// 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;
@ -837,15 +874,24 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3&
glm::vec3 localAcceleration = inverseRot * acceleration;
float minDistance = FLT_MAX;
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)) {
checkPossibleParabolicIntersectionWithTriangle(possibleDistances.x, minDistance, origin, velocity, acceleration,
localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface);
checkPossibleParabolicIntersectionWithTriangle(possibleDistances.y, minDistance, origin, velocity, acceleration,
if (fabsf(localAcceleration.z) < EPSILON) {
if (fabsf(localVelocity.z) < EPSILON) {
return false;
}
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)) {
checkPossibleParabolicIntersectionWithTriangle(possibleDistances.x, minDistance, origin, velocity, acceleration,
localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface);
checkPossibleParabolicIntersectionWithTriangle(possibleDistances.y, minDistance, origin, velocity, acceleration,
localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface);
}
}
if (minDistance < FLT_MAX) {
parabolicDistance = minDistance;
@ -854,6 +900,85 @@ bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3&
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 swingTwistDecomposition(const glm::quat& rotation,
const glm::vec3& direction,
glm::quat& swing,
@ -1100,7 +1225,7 @@ bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) {
}
// 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) {
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;
@ -1221,11 +1346,5 @@ 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) {
a = 1.0f;
b = b / a;
c = c / a;
d = d / a;
e = e / a;
return solve_quartic(b, c, d, e, roots);
return solve_quartic(b / a, c / a, d / a, e / a, roots);
}

View file

@ -97,6 +97,9 @@ bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& ve
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);
/// \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)