From 175e5d5f798911a75ba8b265913008bb668661ed Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 2 Oct 2014 20:44:49 -0700 Subject: [PATCH] add ray picking to 3D overlays --- examples/libraries/entitySelectionTool.js | 69 ++++++++++--- examples/newEditEntities.js | 3 + interface/src/Application.cpp | 1 + interface/src/ui/overlays/Base3DOverlay.cpp | 12 ++- interface/src/ui/overlays/Base3DOverlay.h | 5 +- .../src/ui/overlays/BillboardOverlay.cpp | 18 ++++ interface/src/ui/overlays/BillboardOverlay.h | 2 + interface/src/ui/overlays/Overlay.h | 1 - interface/src/ui/overlays/Overlays.cpp | 99 +++++++++++++++++++ interface/src/ui/overlays/Overlays.h | 21 +++- interface/src/ui/overlays/Volume3DOverlay.cpp | 10 ++ interface/src/ui/overlays/Volume3DOverlay.h | 3 + 12 files changed, 228 insertions(+), 16 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index c95ffbbb68..a4a5272142 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -14,6 +14,7 @@ SelectionDisplay = (function () { var that = {}; + var overlayNames = new Array(); var lastAvatarPosition = MyAvatar.position; var lastAvatarOrientation = MyAvatar.orientation; @@ -217,6 +218,48 @@ SelectionDisplay = (function () { isFacingAvatar: false }); + overlayNames[highlightBox] = "highlightBox"; + overlayNames[selectionBox] = "selectionBox"; + overlayNames[baseOfEntityProjectionOverlay] = "baseOfEntityProjectionOverlay"; + overlayNames[grabberMoveUp] = "grabberMoveUp"; + overlayNames[grabberLBN] = "grabberLBN"; + overlayNames[grabberLBF] = "grabberLBF"; + overlayNames[grabberRBN] = "grabberRBN"; + overlayNames[grabberRBF] = "grabberRBF"; + overlayNames[grabberLTN] = "grabberLTN"; + overlayNames[grabberLTF] = "grabberLTF"; + overlayNames[grabberRTN] = "grabberRTN"; + overlayNames[grabberRTF] = "grabberRTF"; + + overlayNames[grabberTOP] = "grabberTOP"; + overlayNames[grabberBOTTOM] = "grabberBOTTOM"; + overlayNames[grabberLEFT] = "grabberLEFT"; + overlayNames[grabberRIGHT] = "grabberRIGHT"; + overlayNames[grabberNEAR] = "grabberNEAR"; + overlayNames[grabberFAR] = "grabberFAR"; + + overlayNames[grabberEdgeTR] = "grabberEdgeTR"; + overlayNames[grabberEdgeTL] = "grabberEdgeTL"; + overlayNames[grabberEdgeTF] = "grabberEdgeTF"; + overlayNames[grabberEdgeTN] = "grabberEdgeTN"; + overlayNames[grabberEdgeBR] = "grabberEdgeBR"; + overlayNames[grabberEdgeBL] = "grabberEdgeBL"; + overlayNames[grabberEdgeBF] = "grabberEdgeBF"; + overlayNames[grabberEdgeBN] = "grabberEdgeBN"; + overlayNames[grabberEdgeNR] = "grabberEdgeNR"; + overlayNames[grabberEdgeNL] = "grabberEdgeNL"; + overlayNames[grabberEdgeFR] = "grabberEdgeFR"; + overlayNames[grabberEdgeFL] = "grabberEdgeFL"; + + overlayNames[yawHandle] = "yawHandle"; + overlayNames[pitchHandle] = "pitchHandle"; + overlayNames[rollHandle] = "rollHandle"; + + overlayNames[rotateOverlayInner] = "rotateOverlayInner"; + overlayNames[rotateOverlayOuter] = "rotateOverlayOuter"; + overlayNames[rotateOverlayCurrent] = "rotateOverlayCurrent"; + + that.cleanup = function () { Overlays.deleteOverlay(highlightBox); Overlays.deleteOverlay(selectionBox); @@ -579,22 +622,26 @@ print("select()...... entityID:" + entityID.id); that.checkMove = function() { if (currentSelection.isKnownID && (!Vec3.equal(MyAvatar.position, lastAvatarPosition) || !Quat.equal(MyAvatar.orientation, lastAvatarOrientation))){ - -print("checkMove calling .... select()"); - -//print("Vec3.equal(MyAvatar.position, lastAvatarPosition):" + Vec3.equal(MyAvatar.position, lastAvatarPosition); -//Vec3.print("MyAvatar.position:", MyAvatar.position); -//Vec3.print("lastAvatarPosition:", lastAvatarPosition); - -//print("Quat.equal(MyAvatar.orientation, lastAvatarOrientation):" + Quat.equal(MyAvatar.orientation, lastAvatarOrientation)); -//Quat.print("MyAvatar.orientation:", MyAvatar.orientation); -//Quat.print("lastAvatarOrientation:", lastAvatarOrientation); - that.select(currentSelection); } }; that.mousePressEvent = function(event) { + print("SelectionDisplay.mousePressEvent() x:" + event.x + " y:" + event.y); + var pickRay = Camera.computePickRay(event.x, event.y); + + var result = Overlays.findRayIntersection(pickRay); + if (result.intersects) { + print("something intersects... "); + print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]"); + + print(" result.intersects:" + result.intersects); + print(" result.overlayID:" + result.overlayID); + print(" result.distance:" + result.distance); + print(" result.face:" + result.face); + Vec3.print(" result.intersection:", result.intersection); + + } }; that.mouseMoveEvent = function(event) { diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index d8682dce5f..f1abf97b50 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -225,6 +225,9 @@ var toolBar = (function () { if (activeButton === toolBar.clicked(clickedOverlay)) { isActive = !isActive; + if (!isActive) { + selectionDisplay.unselectAll(); + } return true; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec40056299..485691e661 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3874,6 +3874,7 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser connect(scriptEngine, SIGNAL(loadScript(const QString&, bool)), this, SLOT(loadScript(const QString&, bool))); scriptEngine->registerGlobalObject("Overlays", &_overlays); + qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance()); scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 9c67f56e0b..0e68347eca 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -25,8 +25,8 @@ Base3DOverlay::Base3DOverlay() : _position(DEFAULT_POSITION), _lineWidth(DEFAULT_LINE_WIDTH), _isSolid(DEFAULT_IS_SOLID), - _isDashedLine(DEFAULT_IS_DASHED_LINE), - _rotation() + _rotation(), + _isDashedLine(DEFAULT_IS_DASHED_LINE) { } @@ -114,6 +114,12 @@ void Base3DOverlay::setProperties(const QScriptValue& properties) { } } +bool Base3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + return false; +} + + void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) { glBegin(GL_LINES); @@ -145,3 +151,5 @@ void Base3DOverlay::drawDashedLine(const glm::vec3& start, const glm::vec3& end) } + + diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index b6094be08f..62d6826ed0 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -13,7 +13,8 @@ #include #include -//#include + +#include #include "Overlay.h" @@ -42,6 +43,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: void drawDashedLine(const glm::vec3& start, const glm::vec3& end); diff --git a/interface/src/ui/overlays/BillboardOverlay.cpp b/interface/src/ui/overlays/BillboardOverlay.cpp index 5c5fea2a14..d42fe533de 100644 --- a/interface/src/ui/overlays/BillboardOverlay.cpp +++ b/interface/src/ui/overlays/BillboardOverlay.cpp @@ -80,6 +80,7 @@ void BillboardOverlay::render() { glBegin(GL_QUADS); { glTexCoord2f((float)_fromImage.x() / (float)_size.width(), (float)_fromImage.y() / (float)_size.height()); + glVertex2f(-x, -y); glTexCoord2f(((float)_fromImage.x() + (float)_fromImage.width()) / (float)_size.width(), (float)_fromImage.y() / (float)_size.height()); @@ -161,3 +162,20 @@ void BillboardOverlay::replyFinished() { _billboard = reply->readAll(); _isLoaded = true; } + +bool BillboardOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + if (_billboardTexture) { + float maxSize = glm::max(_fromImage.width(), _fromImage.height()); + float x = _fromImage.width() / (2.0f * maxSize); + float y = -_fromImage.height() / (2.0f * maxSize); + float maxDimension = glm::max(x,y); + float scaledDimension = maxDimension * _scale; + glm::vec3 corner = getCenter() - glm::vec3(scaledDimension, scaledDimension, scaledDimension) ; + AACube myCube(corner, scaledDimension * 2.0f); + return myCube.findRayIntersection(origin, direction, distance, face); + } + return false; +} + diff --git a/interface/src/ui/overlays/BillboardOverlay.h b/interface/src/ui/overlays/BillboardOverlay.h index c1731343bf..a0b76869b3 100644 --- a/interface/src/ui/overlays/BillboardOverlay.h +++ b/interface/src/ui/overlays/BillboardOverlay.h @@ -26,6 +26,8 @@ public: virtual void render(); virtual void setProperties(const QScriptValue& properties); void setClipFromSource(const QRect& bounds) { _fromImage = bounds; } + + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; private slots: void replyFinished(); diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 623cd4081c..dfa9b11d74 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -74,7 +74,6 @@ public: void setColorPulse(float value) { _colorPulse = value; } void setAlphaPulse(float value) { _alphaPulse = value; } - virtual void setProperties(const QScriptValue& properties); protected: diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 4f6c37e96d..8511d79aca 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -8,6 +8,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include "BillboardOverlay.h" @@ -255,6 +256,104 @@ unsigned int Overlays::getOverlayAtPoint(const glm::vec2& point) { return 0; // not found } +RayToOverlayIntersectionResult Overlays::findRayIntersection(const PickRay& ray) { + float bestDistance = std::numeric_limits::max(); + RayToOverlayIntersectionResult result; + QMapIterator i(_overlays3D); + i.toBack(); + while (i.hasPrevious()) { + i.previous(); + unsigned int thisID = i.key(); + Base3DOverlay* thisOverlay = static_cast(i.value()); + if (thisOverlay->getVisible() && thisOverlay->isLoaded()) { + float thisDistance; + BoxFace thisFace; + if (thisOverlay->findRayIntersection(ray.origin, ray.direction, thisDistance, thisFace)) { + if (thisDistance < bestDistance) { + bestDistance = thisDistance; + result.intersects = true; + result.distance = thisDistance; + result.face = thisFace; + result.overlayID = thisID; + result.intersection = ray.origin + (ray.direction * thisDistance); + } + } + } + } + return result; +} + +RayToOverlayIntersectionResult::RayToOverlayIntersectionResult() : + intersects(false), + overlayID(-1), + distance(0), + face(), + intersection() +{ +} + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + obj.setProperty("overlayID", value.overlayID); + obj.setProperty("distance", value.distance); + + QString faceName = ""; + // handle BoxFace + switch (value.face) { + case MIN_X_FACE: + faceName = "MIN_X_FACE"; + break; + case MAX_X_FACE: + faceName = "MAX_X_FACE"; + break; + case MIN_Y_FACE: + faceName = "MIN_Y_FACE"; + break; + case MAX_Y_FACE: + faceName = "MAX_Y_FACE"; + break; + case MIN_Z_FACE: + faceName = "MIN_Z_FACE"; + break; + case MAX_Z_FACE: + faceName = "MAX_Z_FACE"; + break; + case UNKNOWN_FACE: + faceName = "UNKNOWN_FACE"; + break; + } + obj.setProperty("face", faceName); + QScriptValue intersection = vec3toScriptValue(engine, value.intersection); + obj.setProperty("intersection", intersection); + return obj; +} + +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + value.overlayID = object.property("overlayID").toVariant().toInt(); + value.distance = object.property("distance").toVariant().toFloat(); + + QString faceName = object.property("face").toVariant().toString(); + if (faceName == "MIN_X_FACE") { + value.face = MIN_X_FACE; + } else if (faceName == "MAX_X_FACE") { + value.face = MAX_X_FACE; + } else if (faceName == "MIN_Y_FACE") { + value.face = MIN_Y_FACE; + } else if (faceName == "MAX_Y_FACE") { + value.face = MAX_Y_FACE; + } else if (faceName == "MIN_Z_FACE") { + value.face = MIN_Z_FACE; + } else { + value.face = MAX_Z_FACE; + }; + QScriptValue intersection = object.property("intersection"); + if (intersection.isValid()) { + vec3FromScriptValue(intersection, value.intersection); + } +} + bool Overlays::isLoaded(unsigned int id) { QReadLocker lock(&_lock); Overlay* overlay = _overlays2D.value(id); diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index 8bd8224f82..6676994eed 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -15,6 +15,21 @@ #include "Overlay.h" +class RayToOverlayIntersectionResult { +public: + RayToOverlayIntersectionResult(); + bool intersects; + int overlayID; + float distance; + BoxFace face; + glm::vec3 intersection; +}; + +Q_DECLARE_METATYPE(RayToOverlayIntersectionResult); + +QScriptValue RayToOverlayIntersectionResultToScriptValue(QScriptEngine* engine, const RayToOverlayIntersectionResult& value); +void RayToOverlayIntersectionResultFromScriptValue(const QScriptValue& object, RayToOverlayIntersectionResult& value); + class Overlays : public QObject { Q_OBJECT public: @@ -36,8 +51,11 @@ public slots: /// deletes a particle void deleteOverlay(unsigned int id); - /// returns the top most overlay at the screen point, or 0 if not overlay at that point + /// returns the top most 2D overlay at the screen point, or 0 if not overlay at that point unsigned int getOverlayAtPoint(const glm::vec2& point); + + /// returns details about the closest 3D Overlay hit by the pick ray + RayToOverlayIntersectionResult findRayIntersection(const PickRay& ray); /// returns whether the overlay's assets are loaded or not bool isLoaded(unsigned int id); @@ -52,5 +70,6 @@ private: QReadWriteLock _deleteLock; }; + #endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index 4dfeed33a1..11e3e5d8cf 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -12,6 +12,7 @@ #include "InterfaceConfig.h" #include +#include #include #include @@ -80,3 +81,12 @@ void Volume3DOverlay::setProperties(const QScriptValue& properties) { } } } + +bool Volume3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face) const { + + // TODO: this is not exactly accurate because it doesn't properly handle rotation... but it's close enough for our + // current use cases. We do need to improve it to be more accurate + AABox myBox(getCorner(), _dimensions); + return myBox.findRayIntersection(origin, direction, distance, face); +} diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 35c0567cc5..7cde169c30 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -30,6 +30,7 @@ public: // getters const glm::vec3& getCenter() const { return _position; } // TODO: consider adding registration point!! + glm::vec3 getCorner() const { return _position - (_dimensions * 0.5f); } // TODO: consider adding registration point!! const glm::vec3& getDimensions() const { return _dimensions; } // setters @@ -38,6 +39,8 @@ public: virtual void setProperties(const QScriptValue& properties); + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) const; + protected: glm::vec3 _dimensions; };