From efd0580cfbd4d0793db4c6f5ae5ea4e15f35d35b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 23 May 2014 17:09:37 -0700 Subject: [PATCH] add ray picking to the model scripting interface --- examples/rayPickExample.js | 16 +++ libraries/models/src/ModelTreeElement.cpp | 31 +++++ libraries/models/src/ModelTreeElement.h | 5 + .../models/src/ModelsScriptingInterface.cpp | 112 ++++++++++++++++++ .../models/src/ModelsScriptingInterface.h | 35 +++++- libraries/octree/src/Octree.cpp | 26 ++-- libraries/octree/src/Octree.h | 1 + libraries/octree/src/OctreeElement.cpp | 49 ++++++++ libraries/octree/src/OctreeElement.h | 8 ++ libraries/script-engine/src/LocalVoxels.cpp | 2 +- libraries/script-engine/src/ScriptEngine.cpp | 1 + .../voxels/src/VoxelsScriptingInterface.cpp | 2 +- 12 files changed, 268 insertions(+), 20 deletions(-) diff --git a/examples/rayPickExample.js b/examples/rayPickExample.js index 9b08a76a2a..336fbe2162 100644 --- a/examples/rayPickExample.js +++ b/examples/rayPickExample.js @@ -41,6 +41,22 @@ function mouseMoveEvent(event) { print("voxelAt.x/y/z/s=" + voxelAt.x + ", " + voxelAt.y + ", " + voxelAt.z + ": " + voxelAt.s); print("voxelAt.red/green/blue=" + voxelAt.red + ", " + voxelAt.green + ", " + voxelAt.blue); } + + intersection = Models.findRayIntersection(pickRay); + if (!intersection.accurate) { + print(">>> NOTE: intersection not accurate. will try calling Models.findRayIntersectionBlocking()"); + intersection = Models.findRayIntersectionBlocking(pickRay); + print(">>> AFTER BLOCKING CALL intersection.accurate=" + intersection.accurate); + } + + if (intersection.intersects) { + print("intersection modelID.id=" + intersection.modelID.id); + print("intersection modelProperties.modelURL=" + intersection.modelProperties.modelURL); + print("intersection face=" + intersection.face); + print("intersection distance=" + intersection.distance); + print("intersection intersection.x/y/z=" + intersection.intersection.x + ", " + + intersection.intersection.y + ", " + intersection.intersection.z); + } } Controller.mouseMoveEvent.connect(mouseMoveEvent); diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index 4398e2cb3c..dcb3253ef4 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -140,6 +140,37 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) { } } +bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject) { + + // only called if we do intersect our bounding cube, but find if we actually intersect with models... + + QList::iterator modelItr = _modelItems->begin(); + QList::const_iterator modelEnd = _modelItems->end(); + bool somethingIntersected = false; + while(modelItr != modelEnd) { + ModelItem& model = (*modelItr); + + AACube modelCube = model.getAACube(); + float localDistance; + BoxFace localFace; + + // if the ray doesn't intersect with our cube, we can stop searching! + if (modelCube.findRayIntersection(origin, direction, localDistance, localFace)) { + if (localDistance < distance) { + distance = localDistance; + face = localFace; + *intersectedObject = (void*)(&model); + somethingIntersected = true; + } + } + + ++modelItr; + } + return somethingIntersected; +} + bool ModelTreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { QList::iterator modelItr = _modelItems->begin(); diff --git a/libraries/models/src/ModelTreeElement.h b/libraries/models/src/ModelTreeElement.h index acb594c33d..8d2f5064bd 100644 --- a/libraries/models/src/ModelTreeElement.h +++ b/libraries/models/src/ModelTreeElement.h @@ -100,6 +100,11 @@ public: virtual bool isRendered() const { return getShouldRender(); } virtual bool deleteApproved() const { return !hasModels(); } + virtual bool canRayIntersect() const { return hasModels(); } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject); + virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; diff --git a/libraries/models/src/ModelsScriptingInterface.cpp b/libraries/models/src/ModelsScriptingInterface.cpp index 7625eef998..7e08571fe5 100644 --- a/libraries/models/src/ModelsScriptingInterface.cpp +++ b/libraries/models/src/ModelsScriptingInterface.cpp @@ -173,3 +173,115 @@ QVector ModelsScriptingInterface::findModels(const glm::vec3& cente return result; } +RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersection(const PickRay& ray) { + return findRayIntersectionWorker(ray, Octree::TryLock); +} + +RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersectionBlocking(const PickRay& ray) { + return findRayIntersectionWorker(ray, Octree::Lock); +} + +RayToModelIntersectionResult ModelsScriptingInterface::findRayIntersectionWorker(const PickRay& ray, + Octree::lockType lockType) { + RayToModelIntersectionResult result; + if (_modelTree) { + OctreeElement* element; + ModelItem* intersectedModel; + result.intersects = _modelTree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + (void**)&intersectedModel, lockType, &result.accurate); + if (result.intersects && intersectedModel) { + result.modelID = intersectedModel->getModelItemID(); + result.modelProperties = intersectedModel->getProperties(); + result.intersection = ray.origin + (ray.direction * result.distance); + } + } + return result; +} + + +RayToModelIntersectionResult::RayToModelIntersectionResult() : + intersects(false), + accurate(true), // assume it's accurate + modelID(), + modelProperties(), + distance(0), + face() +{ +}; + +QScriptValue RayToModelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToModelIntersectionResult& value) { + QScriptValue obj = engine->newObject(); + obj.setProperty("intersects", value.intersects); + obj.setProperty("accurate", value.accurate); + QScriptValue modelItemValue = ModelItemIDtoScriptValue(engine, value.modelID); + obj.setProperty("modelID", modelItemValue); + + QScriptValue modelPropertiesValue = ModelItemPropertiesToScriptValue(engine, value.modelProperties); + obj.setProperty("modelProperties", modelPropertiesValue); + + 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 RayToModelIntersectionResultFromScriptValue(const QScriptValue& object, RayToModelIntersectionResult& value) { + value.intersects = object.property("intersects").toVariant().toBool(); + value.accurate = object.property("accurate").toVariant().toBool(); + QScriptValue modelIDValue = object.property("modelID"); + if (modelIDValue.isValid()) { + ModelItemIDfromScriptValue(modelIDValue, value.modelID); + } + QScriptValue modelPropertiesValue = object.property("modelProperties"); + if (modelPropertiesValue.isValid()) { + ModelItemPropertiesFromScriptValue(modelPropertiesValue, value.modelProperties); + } + 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); + } +} diff --git a/libraries/models/src/ModelsScriptingInterface.h b/libraries/models/src/ModelsScriptingInterface.h index bf8e193f25..c723c9071a 100644 --- a/libraries/models/src/ModelsScriptingInterface.h +++ b/libraries/models/src/ModelsScriptingInterface.h @@ -15,10 +15,30 @@ #include #include - +#include #include +#include + #include "ModelEditPacketSender.h" +class RayToModelIntersectionResult { +public: + RayToModelIntersectionResult(); + bool intersects; + bool accurate; + ModelItemID modelID; + ModelItemProperties modelProperties; + float distance; + BoxFace face; + glm::vec3 intersection; +}; + +Q_DECLARE_METATYPE(RayToModelIntersectionResult) + +QScriptValue RayToModelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToModelIntersectionResult& results); +void RayToModelIntersectionResultFromScriptValue(const QScriptValue& object, RayToModelIntersectionResult& results); + + /// handles scripting of Model commands from JS passed to assigned clients class ModelsScriptingInterface : public OctreeScriptingInterface { Q_OBJECT @@ -59,6 +79,16 @@ public slots: /// this function will not find any models in script engine contexts which don't have access to models QVector findModels(const glm::vec3& center, float radius) const; + /// If the scripting context has visible voxels, this will determine a ray intersection, the results + /// may be inaccurate if the engine is unable to access the visible voxels, in which case result.accurate + /// will be false. + Q_INVOKABLE RayToModelIntersectionResult findRayIntersection(const PickRay& ray); + + /// If the scripting context has visible voxels, this will determine a ray intersection, and will block in + /// order to return an accurate result + Q_INVOKABLE RayToModelIntersectionResult findRayIntersectionBlocking(const PickRay& ray); + + signals: void modelCollisionWithVoxel(const ModelItemID& modelID, const VoxelDetail& voxel, const CollisionInfo& collision); void modelCollisionWithModel(const ModelItemID& idA, const ModelItemID& idB, const CollisionInfo& collision); @@ -66,6 +96,9 @@ signals: private: void queueModelMessage(PacketType packetType, ModelItemID modelID, const ModelItemProperties& properties); + /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode + RayToModelIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType); + uint32_t _nextCreatorTokenID; ModelTree* _modelTree; }; diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index ae00bf5a58..3ec1871023 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -596,34 +596,26 @@ public: OctreeElement*& element; float& distance; BoxFace& face; + void** intersectedObject; bool found; }; bool findRayIntersectionOp(OctreeElement* element, void* extraData) { RayArgs* args = static_cast(extraData); - AACube box = element->getAACube(); - float distance; - BoxFace face; - if (!box.findRayIntersection(args->origin, args->direction, distance, face)) { - return false; - } - if (!element->isLeaf()) { - return true; // recurse on children - } - distance *= TREE_SCALE; - if (element->hasContent() && (!args->found || distance < args->distance)) { - args->element = element; - args->distance = distance; - args->face = face; + + bool keepSearching = true; + if (element->findRayIntersection(args->origin, args->direction, keepSearching, + args->element, args->distance, args->face, args->intersectedObject)) { args->found = true; } - return false; + return keepSearching; } bool Octree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElement*& element, float& distance, BoxFace& face, + OctreeElement*& element, float& distance, BoxFace& face, void** intersectedObject, Octree::lockType lockType, bool* accurateResult) { - RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, false}; + RayArgs args = { origin / (float)(TREE_SCALE), direction, element, distance, face, intersectedObject, false}; + distance = FLT_MAX; bool gotLock = false; if (lockType == Octree::Lock) { diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 496b4016fe..7ab22598ef 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -268,6 +268,7 @@ public: bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElement*& node, float& distance, BoxFace& face, + void** intersectedObject = NULL, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject = NULL, diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 8f52fec179..22320b5969 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1302,6 +1302,55 @@ void OctreeElement::notifyUpdateHooks() { } } +bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject) { + + keepSearching = true; // assume that we will continue searching after this. + + // by default, we only allow intersections with leaves with content + if (!canRayIntersect()) { + return false; // we don't intersect with non-leaves, and we keep searching + } + + AACube cube = getAACube(); + float localDistance; + BoxFace localFace; + + // if the ray doesn't intersect with our cube, we can stop searching! + if (!cube.findRayIntersection(origin, direction, localDistance, localFace)) { + keepSearching = false; // no point in continuing to search + return false; // we did not intersect + } + + // we did hit this element, so calculate appropriate distances + localDistance *= TREE_SCALE; + if (localDistance < distance) { + if (findDetailedRayIntersection(origin, direction, keepSearching, + element, distance, face, intersectedObject)) { + distance = localDistance; + face = localFace; + return true; + } + } + return false; +} + +bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject) { + + // we did hit this element, so calculate appropriate distances + if (hasContent()) { + element = this; + if (intersectedObject) { + *intersectedObject = this; + } + return true; // we did intersect + } + return false; // we did not intersect +} + bool OctreeElement::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const { return _cube.findSpherePenetration(center, radius, penetration); diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index 24e4a4a6ac..362c0ce560 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -102,6 +102,14 @@ public: virtual bool deleteApproved() const { return true; } + virtual bool canRayIntersect() const { return isLeaf(); } + virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& node, float& distance, BoxFace& face, + void** intersectedObject = NULL); + + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const; diff --git a/libraries/script-engine/src/LocalVoxels.cpp b/libraries/script-engine/src/LocalVoxels.cpp index 27952b0998..9a6fb2b6d1 100644 --- a/libraries/script-engine/src/LocalVoxels.cpp +++ b/libraries/script-engine/src/LocalVoxels.cpp @@ -130,7 +130,7 @@ RayToVoxelIntersectionResult LocalVoxels::findRayIntersectionWorker(const PickRa RayToVoxelIntersectionResult result; if (_tree) { OctreeElement* element; - result.intersects = _tree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + result.intersects = _tree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, NULL, lockType, &result.accurate); if (result.intersects) { VoxelTreeElement* voxel = (VoxelTreeElement*)element; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 420529bb6c..2c17090914 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -224,6 +224,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(&_engine, ModelItemPropertiesToScriptValue, ModelItemPropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ModelItemIDtoScriptValue, ModelItemIDfromScriptValue); + qScriptRegisterMetaType(&_engine, RayToModelIntersectionResultToScriptValue, RayToModelIntersectionResultFromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index 3f69867530..2aad0f7a76 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -135,7 +135,7 @@ RayToVoxelIntersectionResult VoxelsScriptingInterface::findRayIntersectionWorker RayToVoxelIntersectionResult result; if (_tree) { OctreeElement* element; - result.intersects = _tree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, + result.intersects = _tree->findRayIntersection(ray.origin, ray.direction, element, result.distance, result.face, NULL, lockType, &result.accurate); if (result.intersects) { VoxelTreeElement* voxel = (VoxelTreeElement*)element;