From 8ec14568fe72bbf3468004f38a9b2614484a10a1 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 27 May 2014 13:39:32 -0700 Subject: [PATCH 1/2] support ray picking against the AABB for the rotated model extents --- interface/src/models/ModelTreeRenderer.cpp | 55 ++++++++-------------- interface/src/models/ModelTreeRenderer.h | 5 +- libraries/fbx/src/FBXReader.cpp | 37 +++++++++++++++ libraries/fbx/src/FBXReader.h | 5 +- libraries/models/src/ModelTree.h | 11 +++++ libraries/models/src/ModelTreeElement.cpp | 37 ++++++++++++++- 6 files changed, 111 insertions(+), 39 deletions(-) diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 6d7c61dca1..8ffd61043a 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -11,6 +11,8 @@ #include +#include + #include "InterfaceConfig.h" #include "Menu.h" #include "ModelTreeRenderer.h" @@ -34,8 +36,13 @@ ModelTreeRenderer::~ModelTreeRenderer() { void ModelTreeRenderer::init() { OctreeRenderer::init(); + static_cast(_tree)->setFBXService(this); } +void ModelTreeRenderer::setTree(Octree* newTree) { + OctreeRenderer::setTree(newTree); + static_cast(_tree)->setFBXService(this); +} void ModelTreeRenderer::update() { if (_tree) { @@ -48,6 +55,16 @@ void ModelTreeRenderer::render(RenderMode renderMode) { OctreeRenderer::render(renderMode); } +const FBXGeometry* ModelTreeRenderer::getGeometryForModel(const ModelItem& modelItem) { + const FBXGeometry* result = NULL; + Model* model = getModel(modelItem); + if (model) { + result = &model->getGeometry()->getFBXGeometry(); + + } + return result; +} + Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { Model* model = NULL; @@ -73,42 +90,6 @@ Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { return model; } -void calculateRotatedExtents(Extents& extents, const glm::quat& rotation) { - glm::vec3 bottomLeftNear(extents.minimum.x, extents.minimum.y, extents.minimum.z); - glm::vec3 bottomRightNear(extents.maximum.x, extents.minimum.y, extents.minimum.z); - glm::vec3 bottomLeftFar(extents.minimum.x, extents.minimum.y, extents.maximum.z); - glm::vec3 bottomRightFar(extents.maximum.x, extents.minimum.y, extents.maximum.z); - glm::vec3 topLeftNear(extents.minimum.x, extents.maximum.y, extents.minimum.z); - glm::vec3 topRightNear(extents.maximum.x, extents.maximum.y, extents.minimum.z); - glm::vec3 topLeftFar(extents.minimum.x, extents.maximum.y, extents.maximum.z); - glm::vec3 topRightFar(extents.maximum.x, extents.maximum.y, extents.maximum.z); - - glm::vec3 bottomLeftNearRotated = rotation * bottomLeftNear; - glm::vec3 bottomRightNearRotated = rotation * bottomRightNear; - glm::vec3 bottomLeftFarRotated = rotation * bottomLeftFar; - glm::vec3 bottomRightFarRotated = rotation * bottomRightFar; - glm::vec3 topLeftNearRotated = rotation * topLeftNear; - glm::vec3 topRightNearRotated = rotation * topRightNear; - glm::vec3 topLeftFarRotated = rotation * topLeftFar; - glm::vec3 topRightFarRotated = rotation * topRightFar; - - extents.minimum = glm::min(bottomLeftNearRotated, - glm::min(bottomRightNearRotated, - glm::min(bottomLeftFarRotated, - glm::min(bottomRightFarRotated, - glm::min(topLeftNearRotated, - glm::min(topRightNearRotated, - glm::min(topLeftFarRotated,topRightFarRotated))))))); - - extents.maximum = glm::max(bottomLeftNearRotated, - glm::max(bottomRightNearRotated, - glm::max(bottomLeftFarRotated, - glm::max(bottomRightFarRotated, - glm::max(topLeftNearRotated, - glm::max(topRightNearRotated, - glm::max(topLeftFarRotated,topRightFarRotated))))))); -} - void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { args->_elementsTouched++; // actually render it here... @@ -242,6 +223,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) model->render(alpha, modelRenderMode); if (!isShadowMode && displayModelBounds) { + glm::vec3 unRotatedMinimum = model->getUnscaledMeshExtents().minimum; glm::vec3 unRotatedMaximum = model->getUnscaledMeshExtents().maximum; glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; @@ -279,6 +261,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) glutWireCube(1.0); glPopMatrix(); + } glPopMatrix(); diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h index e0b8d7d0a2..2d418b1a25 100644 --- a/interface/src/models/ModelTreeRenderer.h +++ b/interface/src/models/ModelTreeRenderer.h @@ -26,7 +26,7 @@ #include "renderer/Model.h" // Generic client side Octree renderer class. -class ModelTreeRenderer : public OctreeRenderer { +class ModelTreeRenderer : public OctreeRenderer, public ModelItemFBXService { public: ModelTreeRenderer(); virtual ~ModelTreeRenderer(); @@ -38,6 +38,7 @@ public: virtual void renderElement(OctreeElement* element, RenderArgs* args); virtual float getSizeScale() const; virtual int getBoundaryLevelAdjust() const; + virtual void setTree(Octree* newTree); void update(); @@ -48,6 +49,8 @@ public: virtual void init(); virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); + virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem); + protected: Model* getModel(const ModelItem& modelItem); QMap _knownModelsItemModels; diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 713870dafb..60412cf0ce 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2052,3 +2052,40 @@ FBXGeometry readSVO(const QByteArray& model) { return geometry; } + +void calculateRotatedExtents(Extents& extents, const glm::quat& rotation) { + glm::vec3 bottomLeftNear(extents.minimum.x, extents.minimum.y, extents.minimum.z); + glm::vec3 bottomRightNear(extents.maximum.x, extents.minimum.y, extents.minimum.z); + glm::vec3 bottomLeftFar(extents.minimum.x, extents.minimum.y, extents.maximum.z); + glm::vec3 bottomRightFar(extents.maximum.x, extents.minimum.y, extents.maximum.z); + glm::vec3 topLeftNear(extents.minimum.x, extents.maximum.y, extents.minimum.z); + glm::vec3 topRightNear(extents.maximum.x, extents.maximum.y, extents.minimum.z); + glm::vec3 topLeftFar(extents.minimum.x, extents.maximum.y, extents.maximum.z); + glm::vec3 topRightFar(extents.maximum.x, extents.maximum.y, extents.maximum.z); + + glm::vec3 bottomLeftNearRotated = rotation * bottomLeftNear; + glm::vec3 bottomRightNearRotated = rotation * bottomRightNear; + glm::vec3 bottomLeftFarRotated = rotation * bottomLeftFar; + glm::vec3 bottomRightFarRotated = rotation * bottomRightFar; + glm::vec3 topLeftNearRotated = rotation * topLeftNear; + glm::vec3 topRightNearRotated = rotation * topRightNear; + glm::vec3 topLeftFarRotated = rotation * topLeftFar; + glm::vec3 topRightFarRotated = rotation * topRightFar; + + extents.minimum = glm::min(bottomLeftNearRotated, + glm::min(bottomRightNearRotated, + glm::min(bottomLeftFarRotated, + glm::min(bottomRightFarRotated, + glm::min(topLeftNearRotated, + glm::min(topRightNearRotated, + glm::min(topLeftFarRotated,topRightFarRotated))))))); + + extents.maximum = glm::max(bottomLeftNearRotated, + glm::max(bottomRightNearRotated, + glm::max(bottomLeftFarRotated, + glm::max(bottomRightFarRotated, + glm::max(topLeftNearRotated, + glm::max(topRightNearRotated, + glm::max(topLeftFarRotated,topRightFarRotated))))))); +} + diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 8073ab36a9..4c93f3dc5e 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -51,7 +51,8 @@ public: bool containsPoint(const glm::vec3& point) const; /// \return whether or not the extents are empty - bool isEmpty() { return minimum == maximum; } + bool isEmpty() const { return minimum == maximum; } + bool isValid() const { return !((minimum == glm::vec3(FLT_MAX)) && (maximum == glm::vec3(-FLT_MAX))); } glm::vec3 minimum; glm::vec3 maximum; @@ -238,4 +239,6 @@ FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping); /// Reads SVO geometry from the supplied model data. FBXGeometry readSVO(const QByteArray& model); +void calculateRotatedExtents(Extents& extents, const glm::quat& rotation); + #endif // hifi_FBXReader_h diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index 596ca9074d..c450f04e2d 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -20,6 +20,11 @@ public: virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 0; }; +class ModelItemFBXService { +public: + virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) = 0; +}; + class ModelTree : public Octree { Q_OBJECT public: @@ -74,6 +79,11 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); void handleAddModelResponse(const QByteArray& packet); + + void setFBXService(ModelItemFBXService* service) { _fbxService = service; } + const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) { + return _fbxService ? _fbxService->getGeometryForModel(modelItem) : NULL; + } private: @@ -96,6 +106,7 @@ private: QReadWriteLock _recentlyDeletedModelsLock; QMultiMap _recentlyDeletedModelItemIDs; + ModelItemFBXService* _fbxService; }; #endif // hifi_ModelTree_h diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index dcb3253ef4..dad592f838 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include "ModelTree.h" @@ -158,7 +159,41 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons // if the ray doesn't intersect with our cube, we can stop searching! if (modelCube.findRayIntersection(origin, direction, localDistance, localFace)) { - if (localDistance < distance) { + const FBXGeometry* fbxGeometry = _myTree->getGeometryForModel(model); + if (fbxGeometry && fbxGeometry->meshExtents.isValid()) { + Extents extents = fbxGeometry->meshExtents; + + // NOTE: these extents are model space, so we need to scale and center them accordingly + // size is our "target size in world space" + // we need to set our model scale so that the extents of the mesh, fit in a cube that size... + float maxDimension = glm::distance(extents.maximum, extents.minimum); + float scale = model.getSize() / maxDimension; + + glm::vec3 halfDimensions = (extents.maximum - extents.minimum) * 0.5f; + glm::vec3 offset = -extents.minimum - halfDimensions; + + extents.minimum += offset; + extents.maximum += offset; + + extents.minimum *= scale; + extents.maximum *= scale; + + calculateRotatedExtents(extents, model.getModelRotation()); + + extents.minimum += model.getPosition(); + extents.maximum += model.getPosition(); + + AABox rotatedExtentsBox(extents.minimum, (extents.maximum - extents.minimum)); + + if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) { + if (localDistance < distance) { + distance = localDistance; + face = localFace; + *intersectedObject = (void*)(&model); + somethingIntersected = true; + } + } + } else if (localDistance < distance) { distance = localDistance; face = localFace; *intersectedObject = (void*)(&model); From 285bd0d2f24e90887b270efa51851d0091ba2053 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 27 May 2014 15:50:39 -0700 Subject: [PATCH 2/2] Switched to findRayIntersection --- examples/editModels.js | 223 ++++++++++++++++++++++------------------- 1 file changed, 120 insertions(+), 103 deletions(-) diff --git a/examples/editModels.js b/examples/editModels.js index d53b854003..30d1e4edf4 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -188,7 +188,7 @@ function controller(wichSide) { var X = Vec3.sum(A, Vec3.multiply(B, x)); var d = Vec3.length(Vec3.subtract(P, X)); - if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { + if (0 < x && x < LASER_LENGTH_FACTOR) { return { valid: true, x: x, y: y, z: z }; } return { valid: false }; @@ -293,39 +293,43 @@ function controller(wichSide) { if (this.pressing) { Vec3.print("Looking at: ", this.palmPosition); - var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); + var pickRay = { origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; + var foundIntersection = Models.findRayIntersection(pickRay); - for (var i = 0; i < foundModels.length; i++) { - - if (!foundModels[i].isKnownID) { - var identify = Models.identifyModel(foundModels[i]); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(update loop)"); - return; - } - foundModels[i] = identify; + if(!foundIntersection.accurate) { + return; + } + var foundModel = foundIntersection.modelID; + + if (!foundModel.isKnownID) { + var identify = Models.identifyModel(foundModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); + continue; } - - var properties = Models.getModelProperties(foundModels[i]); - print("foundModels["+i+"].modelURL=" + properties.modelURL); - - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); - var check = this.checkModel(properties); - if (check.valid) { - this.grab(foundModels[i], properties); - this.x = check.x; - this.y = check.y; - this.z = check.z; - return; - } + foundModel = identify; + } + + var properties = Models.getModelProperties(foundModel); + print("foundModel.modelURL=" + properties.modelURL); + + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + print("Checking properties: " + properties.id + " " + properties.isKnownID); + var check = this.checkModel(properties); + if (check.valid) { + this.grab(foundModel, properties); + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; } } } } - + this.cleanup = function () { Overlays.deleteOverlay(this.laser); Overlays.deleteOverlay(this.ball); @@ -478,93 +482,97 @@ function mousePressEvent(event) { } else { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundModels = Models.findModels(pickRay.origin, LASER_LENGTH_FACTOR); - var closest = -1.0; - for (var i = 0; i < foundModels.length; i++) { - if (!foundModels[i].isKnownID) { - var identify = Models.identifyModel(foundModels[i]); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(update loop)"); - continue; - } - foundModels[i] = identify; - } - - var properties = Models.getModelProperties(foundModels[i]); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - - if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { - if (closest < 0.0) { - closest = x; - } - - if (x <= closest) { - modelSelected = true; - selectedModelID = foundModels[i]; - selectedModelProperties = properties; - - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - } - } + var foundIntersection = Models.findRayIntersection(pickRay); + + if(!foundIntersection.accurate) { + return; + } + var foundModel = foundIntersection.modelID; + + if (!foundModel.isKnownID) { + var identify = Models.identifyModel(foundModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); + continue; } + foundModel = identify; } - if (modelSelected) { - selectedModelProperties.oldRadius = selectedModelProperties.radius; - selectedModelProperties.oldPosition = { - x: selectedModelProperties.position.x, - y: selectedModelProperties.position.y, - z: selectedModelProperties.position.z, - }; - selectedModelProperties.oldRotation = { - x: selectedModelProperties.modelRotation.x, - y: selectedModelProperties.modelRotation.y, - z: selectedModelProperties.modelRotation.z, - w: selectedModelProperties.modelRotation.w, - }; + var properties = Models.getModelProperties(foundModel); + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + print("Checking properties: " + properties.id + " " + properties.isKnownID); + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| - selectedModelProperties.glowLevel = 0.1; - Models.editModel(selectedModelID, { glowLevel: selectedModelProperties.glowLevel}); + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; - print("Clicked on " + selectedModelID.id + " " + modelSelected); + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (0 < x && x < LASER_LENGTH_FACTOR) { + modelSelected = true; + selectedModelID = foundModel; + selectedModelProperties = properties; + + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + } } } + + if (modelSelected) { + selectedModelProperties.oldRadius = selectedModelProperties.radius; + selectedModelProperties.oldPosition = { + x: selectedModelProperties.position.x, + y: selectedModelProperties.position.y, + z: selectedModelProperties.position.z, + }; + selectedModelProperties.oldRotation = { + x: selectedModelProperties.modelRotation.x, + y: selectedModelProperties.modelRotation.y, + z: selectedModelProperties.modelRotation.z, + w: selectedModelProperties.modelRotation.w, + }; + selectedModelProperties.glowLevel = 0.0; + + print("Clicked on " + selectedModelID.id + " " + modelSelected); + } } -Controller.mouseReleaseEvent.connect(function() { - if (modelSelected) { - Models.editModel(selectedModelID, { glowLevel: 0.0 }); - modelSelected = false; - } - }); - +var glowedModelID = { id: -1, isKnownID: false }; var oldModifier = 0; var modifier = 0; var wasShifted = false; function mouseMoveEvent(event) { + var pickRay = Camera.computePickRay(event.x, event.y); + if (!modelSelected) { + var modelIntersection = Models.findRayIntersection(pickRay); + if (modelIntersection.accurate) { + if(glowedModelID.isKnownID && glowedModelID.id != modelIntersection.modelID.id) { + Models.editModel(glowedModelID, { glowLevel: 0.0 }); + glowedModelID.id = -1; + glowedModelID.isKnownID = false; + } + + if (modelIntersection.modelID.isKnownID) { + Models.editModel(modelIntersection.modelID, { glowLevel: 0.25 }); + glowedModelID = modelIntersection.modelID; + } + } return; } @@ -579,8 +587,7 @@ function mouseMoveEvent(event) { } else { modifier = 0; } - - var pickRay = Camera.computePickRay(event.x, event.y); + pickRay = Camera.computePickRay(event.x, event.y); if (wasShifted != event.isShifted || modifier != oldModifier) { selectedModelProperties.oldRadius = selectedModelProperties.radius; @@ -653,6 +660,15 @@ function mouseMoveEvent(event) { Models.editModel(selectedModelID, selectedModelProperties); } +function mouseReleaseEvent(event) { + modelSelected = false; + + glowedModelID.id = -1; + glowedModelID.isKnownID = false; +} + + + function setupModelMenus() { // add our menuitems Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); @@ -677,6 +693,7 @@ Script.scriptEnding.connect(scriptEnding); Script.update.connect(checkController); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); setupModelMenus(); Menu.menuItemEvent.connect(function(menuItem){