diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7e683c7cc8..ad61e6a4a1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6654,7 +6654,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe registerInteractiveWindowMetaType(scriptEngine.data()); - DependencyManager::get()->registerMetaTypes(scriptEngine.data()); + auto pickScriptingInterface = DependencyManager::get(); + pickScriptingInterface->registerMetaTypes(scriptEngine.data()); + pickScriptingInterface->setCollisionWorld(_physicsEngine->getDynamicsWorld()); // connect this script engines printedMessage signal to the global ScriptEngines these various messages connect(scriptEngine.data(), &ScriptEngine::printedMessage, diff --git a/interface/src/raypick/CollisionPick.cpp b/interface/src/raypick/CollisionPick.cpp new file mode 100644 index 0000000000..7ed146300d --- /dev/null +++ b/interface/src/raypick/CollisionPick.cpp @@ -0,0 +1,301 @@ +// +// Created by Sabrina Shanman 7/16/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "CollisionPick.h" + +#include + +#include + +#include "ScriptEngineLogging.h" +#include "model-networking/ModelCache.h" + +bool CollisionPick::isShapeInfoReady(CollisionRegion& pick) { + if (pick.shouldComputeShapeInfo()) { + if (!_cachedResource || _cachedResource->getURL() != pick.modelURL) { + _cachedResource = DependencyManager::get()->getCollisionGeometryResource(pick.modelURL); + } + + if (_cachedResource->isLoaded()) { + computeShapeInfo(pick, pick.shapeInfo, _cachedResource); + } + + return false; + } + + return true; +} + +void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource) { + // This code was copied and modified from RenderableModelEntityItem::computeShapeInfo + // TODO: Move to some shared code area (in entities-renderer? model-networking?) + // after we verify this is working and do a diff comparison with RenderableModelEntityItem::computeShapeInfo + // to consolidate the code. + // We may also want to make computeShapeInfo always abstract away from the gpu model mesh, like it does here. + const uint32_t TRIANGLE_STRIDE = 3; + const uint32_t QUAD_STRIDE = 4; + + ShapeType type = shapeInfo.getType(); + glm::vec3 dimensions = pick.transform.getScale(); + if (type == SHAPE_TYPE_COMPOUND) { + // should never fall in here when collision model not fully loaded + // TODO: assert that all geometries exist and are loaded + //assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded()); + const FBXGeometry& collisionGeometry = resource->getFBXGeometry(); + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + uint32_t i = 0; + + // the way OBJ files get read, each section under a "g" line is its own meshPart. We only expect + // to find one actual "mesh" (with one or more meshParts in it), but we loop over the meshes, just in case. + foreach (const FBXMesh& mesh, collisionGeometry.meshes) { + // each meshPart is a convex hull + foreach (const FBXMeshPart &meshPart, mesh.parts) { + pointCollection.push_back(QVector()); + ShapeInfo::PointList& pointsInPart = pointCollection[i]; + + // run through all the triangles and (uniquely) add each point to the hull + uint32_t numIndices = (uint32_t)meshPart.triangleIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += TRIANGLE_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.triangleIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.triangleIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.triangleIndices[j + 2]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + } + + // run through all the quads and (uniquely) add each point to the hull + numIndices = (uint32_t)meshPart.quadIndices.size(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices % QUAD_STRIDE == 0); + numIndices -= numIndices % QUAD_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + for (uint32_t j = 0; j < numIndices; j += QUAD_STRIDE) { + glm::vec3 p0 = mesh.vertices[meshPart.quadIndices[j]]; + glm::vec3 p1 = mesh.vertices[meshPart.quadIndices[j + 1]]; + glm::vec3 p2 = mesh.vertices[meshPart.quadIndices[j + 2]]; + glm::vec3 p3 = mesh.vertices[meshPart.quadIndices[j + 3]]; + if (!pointsInPart.contains(p0)) { + pointsInPart << p0; + } + if (!pointsInPart.contains(p1)) { + pointsInPart << p1; + } + if (!pointsInPart.contains(p2)) { + pointsInPart << p2; + } + if (!pointsInPart.contains(p3)) { + pointsInPart << p3; + } + } + + if (pointsInPart.size() == 0) { + qCDebug(scriptengine) << "Warning -- meshPart has no faces"; + pointCollection.pop_back(); + continue; + } + ++i; + } + } + + // We expect that the collision model will have the same units and will be displaced + // from its origin in the same way the visual model is. The visual model has + // been centered and probably scaled. We take the scaling and offset which were applied + // to the visual model and apply them to the collision model (without regard for the + // collision model's extents). + + glm::vec3 scaleToFit = dimensions / resource->getFBXGeometry().getUnscaledMeshExtents().size(); + // multiply each point by scale + for (int32_t i = 0; i < pointCollection.size(); i++) { + for (int32_t j = 0; j < pointCollection[i].size(); j++) { + // back compensate for registration so we can apply that offset to the shapeInfo later + pointCollection[i][j] = scaleToFit * pointCollection[i][j]; + } + } + shapeInfo.setParams(type, dimensions, resource->getURL().toString()); + } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { + const FBXGeometry& fbxGeometry = resource->getFBXGeometry(); + int numFbxMeshes = fbxGeometry.meshes.size(); + int totalNumVertices = 0; + glm::mat4 invRegistrationOffset = glm::translate(dimensions * (-ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); + for (int i = 0; i < numFbxMeshes; i++) { + const FBXMesh& mesh = fbxGeometry.meshes.at(i); + totalNumVertices += mesh.vertices.size(); + } + const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; + if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { + qWarning() << "model" << resource->getURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + auto& meshes = resource->getFBXGeometry().meshes; + int32_t numMeshes = (int32_t)(meshes.size()); + + const int MAX_ALLOWED_MESH_COUNT = 1000; + if (numMeshes > MAX_ALLOWED_MESH_COUNT) { + // too many will cause the deadlock timer to throw... + shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions); + return; + } + + ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection(); + pointCollection.clear(); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + pointCollection.resize(numMeshes); + } else { + pointCollection.resize(1); + } + + ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices(); + triangleIndices.clear(); + + Extents extents; + int32_t meshCount = 0; + int32_t pointListIndex = 0; + for (auto& mesh : meshes) { + if (!mesh.vertices.size()) { + continue; + } + QVector vertices = mesh.vertices; + + ShapeInfo::PointList& points = pointCollection[pointListIndex]; + + // reserve room + int32_t sizeToReserve = (int32_t)(vertices.count()); + if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // a list of points for each mesh + pointListIndex++; + } else { + // only one list of points + sizeToReserve += (int32_t)points.size(); + } + points.reserve(sizeToReserve); + + // copy points + uint32_t meshIndexOffset = (uint32_t)points.size(); + const glm::vec3* vertexItr = vertices.cbegin(); + while (vertexItr != vertices.cend()) { + glm::vec3 point = *vertexItr; + points.push_back(point); + extents.addPoint(point); + ++vertexItr; + } + + if (type == SHAPE_TYPE_STATIC_MESH) { + // copy into triangleIndices + size_t triangleIndicesCount = 0; + for (const FBXMeshPart& meshPart : mesh.parts) { + triangleIndicesCount += meshPart.triangleIndices.count(); + } + triangleIndices.reserve(triangleIndicesCount); + + for (const FBXMeshPart& meshPart : mesh.parts) { + const int* indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + triangleIndices.push_back(*indexItr); + ++indexItr; + } + } + } else if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { + // for each mesh copy unique part indices, separated by special bogus (flag) index values + for (const FBXMeshPart& meshPart : mesh.parts) { + // collect unique list of indices for this part + std::set uniqueIndices; + auto numIndices = meshPart.triangleIndices.count(); + // TODO: assert rather than workaround after we start sanitizing FBXMesh higher up + //assert(numIndices% TRIANGLE_STRIDE == 0); + numIndices -= numIndices % TRIANGLE_STRIDE; // WORKAROUND lack of sanity checking in FBXReader + + auto indexItr = meshPart.triangleIndices.cbegin(); + while (indexItr != meshPart.triangleIndices.cend()) { + uniqueIndices.insert(*indexItr); + ++indexItr; + } + + // store uniqueIndices in triangleIndices + triangleIndices.reserve(triangleIndices.size() + (int32_t)uniqueIndices.size()); + for (auto index : uniqueIndices) { + triangleIndices.push_back(index); + } + // flag end of part + triangleIndices.push_back(END_OF_MESH_PART); + } + // flag end of mesh + triangleIndices.push_back(END_OF_MESH); + } + ++meshCount; + } + + // scale and shift + glm::vec3 extentsSize = extents.size(); + glm::vec3 scaleToFit = dimensions / extentsSize; + for (int32_t i = 0; i < 3; ++i) { + if (extentsSize[i] < 1.0e-6f) { + scaleToFit[i] = 1.0f; + } + } + for (auto points : pointCollection) { + for (int32_t i = 0; i < points.size(); ++i) { + points[i] = (points[i] * scaleToFit); + } + } + + shapeInfo.setParams(type, 0.5f * dimensions, resource->getURL().toString()); + } +} + +CollisionRegion CollisionPick::getMathematicalPick() const { + return _mathPick; +} + +PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) { + if (!isShapeInfoReady(*const_cast(&pick))) { + // Cannot compute result + return std::make_shared(); + } + + auto entityCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); + btCollisionWorld* collisionWorld = const_cast(_collisionWorld); + collisionWorld->contactTest(&entityCollisionCallback.collisionObject, entityCollisionCallback); + + return std::make_shared(pick, entityCollisionCallback.intersectingObjects, std::vector()); +} + +PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) { + return getDefaultResult(QVariantMap()); +} + +PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) { + if (!isShapeInfoReady(*const_cast(&pick))) { + // Cannot compute result + return std::make_shared(); + } + + auto avatarCollisionCallback = AllObjectMotionStatesCallback(pick.shapeInfo, pick.transform); + btCollisionWorld* collisionWorld = const_cast(_collisionWorld); + collisionWorld->contactTest(&avatarCollisionCallback.collisionObject, avatarCollisionCallback); + + return std::make_shared(pick, std::vector(), avatarCollisionCallback.intersectingObjects); +} + +PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) { + return getDefaultResult(QVariantMap()); +} \ No newline at end of file diff --git a/interface/src/raypick/CollisionPick.h b/interface/src/raypick/CollisionPick.h new file mode 100644 index 0000000000..5cd8d4ccd6 --- /dev/null +++ b/interface/src/raypick/CollisionPick.h @@ -0,0 +1,238 @@ +// +// Created by Sabrina Shanman 7/11/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_CollisionPick_h +#define hifi_CollisionPick_h + +#include + +#include +#include +#include +#include +#include + +class CollisionPickResult : public PickResult { +public: + struct EntityIntersection { + EntityIntersection() { } + + EntityIntersection(const EntityIntersection& entityIntersection) : + id(entityIntersection.id), + pickCollisionPoint(entityIntersection.pickCollisionPoint), + entityCollisionPoint(entityIntersection.entityCollisionPoint) { + } + + EntityIntersection(QUuid id, glm::vec3 pickCollisionPoint, glm::vec3 entityCollisionPoint) : + id(id), + pickCollisionPoint(pickCollisionPoint), + entityCollisionPoint(entityCollisionPoint) { + } + + QVariantMap toVariantMap() { + QVariantMap variantMap; + variantMap["objectID"] = id; + variantMap["pickCollisionPoint"] = vec3toVariant(pickCollisionPoint); + variantMap["entityCollisionPoint"] = vec3toVariant(entityCollisionPoint); + return variantMap; + } + + QUuid id; + glm::vec3 pickCollisionPoint; + glm::vec3 entityCollisionPoint; + }; + + CollisionPickResult() {} + CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {} + + CollisionPickResult(const CollisionRegion& searchRegion, const std::vector& intersectingEntities, const std::vector& intersectingAvatars) : + PickResult(searchRegion.toVariantMap()), + intersects(intersectingEntities.size() || intersectingAvatars.size()), + intersectingEntities(intersectingEntities), + intersectingAvatars(intersectingAvatars) { + } + + CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) { + intersectingAvatars = collisionPickResult.intersectingAvatars; + intersectingEntities = collisionPickResult.intersectingEntities; + intersects = intersectingAvatars.size() || intersectingEntities.size(); + } + + bool intersects { false }; + std::vector intersectingEntities; + std::vector intersectingAvatars; + + virtual QVariantMap toVariantMap() const override { + QVariantMap variantMap; + + variantMap["intersects"] = intersects; + + QVariantList qIntersectingEntities; + for (auto intersectingEntity : intersectingEntities) { + qIntersectingEntities.append(intersectingEntity.toVariantMap()); + } + variantMap["intersectingEntities"] = qIntersectingEntities; + + QVariantList qIntersectingAvatars; + for (auto intersectingAvatar : intersectingAvatars) { + qIntersectingAvatars.append(intersectingAvatar.toVariantMap()); + } + variantMap["intersectingAvatars"] = qIntersectingAvatars; + + variantMap["collisionRegion"] = pickVariant; + + return variantMap; + } + + bool doesIntersect() const override { return intersects; } + bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return true; } + + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { + const std::shared_ptr newCollisionResult = std::static_pointer_cast(*const_cast(&newRes)); + // Have to reference the raw pointer to work around strange type conversion errors + CollisionPickResult* newCollisionResultRaw = const_cast(newCollisionResult.get()); + + for (EntityIntersection& intersectingEntity : newCollisionResultRaw->intersectingEntities) { + intersectingEntities.push_back(intersectingEntity); + } + for (EntityIntersection& intersectingAvatar : newCollisionResultRaw->intersectingAvatars) { + intersectingAvatars.push_back(intersectingAvatar); + } + + return std::make_shared(*this); + } +}; + +class CollisionPick : public Pick { +public: + CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, const btCollisionWorld* collisionWorld) : + Pick(filter, maxDistance, enabled), + _mathPick(collisionRegion), + _collisionWorld(collisionWorld) { + } + + CollisionRegion getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const { return std::make_shared(pickVariant); } + PickResultPointer getEntityIntersection(const CollisionRegion& pick) override; + PickResultPointer getOverlayIntersection(const CollisionRegion& pick) override; + PickResultPointer getAvatarIntersection(const CollisionRegion& pick) override; + PickResultPointer getHUDIntersection(const CollisionRegion& pick) override; + +protected: + // Returns true if pick.shapeInfo is valid. Otherwise, attempts to get the shapeInfo ready for use. + bool isShapeInfoReady(CollisionRegion& pick); + void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer resource); + + CollisionRegion _mathPick; + const btCollisionWorld* _collisionWorld; + QSharedPointer _cachedResource; +}; + +// Callback for checking the motion states of all colliding rigid bodies for candidacy to be added to a list +struct RigidBodyFilterResultCallback : public btCollisionWorld::ContactResultCallback { + RigidBodyFilterResultCallback(const ShapeInfo& shapeInfo, const Transform& transform) : + btCollisionWorld::ContactResultCallback(), collisionObject() { + const btCollisionShape* collisionShape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); + + collisionObject.setCollisionShape(const_cast(collisionShape)); + + btTransform bulletTransform; + bulletTransform.setOrigin(glmToBullet(transform.getTranslation())); + bulletTransform.setRotation(glmToBullet(transform.getRotation())); + + collisionObject.setWorldTransform(bulletTransform); + } + + ~RigidBodyFilterResultCallback() { + ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape()); + } + + RigidBodyFilterResultCallback(btCollisionObject& testCollisionObject) : + btCollisionWorld::ContactResultCallback(), collisionObject(testCollisionObject) { + } + + btCollisionObject collisionObject; + + // Check candidacy for adding to a list + virtual void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) = 0; + + btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override { + const btCollisionObject* otherBody; + btVector3 point; + btVector3 otherPoint; + if (colObj0->m_collisionObject == &collisionObject) { + otherBody = colObj1->m_collisionObject; + point = cp.m_localPointA; + otherPoint = cp.m_localPointB; + } + else { + otherBody = colObj0->m_collisionObject; + point = cp.m_localPointB; + otherPoint = cp.m_localPointA; + } + const btRigidBody* collisionCandidate = dynamic_cast(otherBody); + if (!collisionCandidate) { + return 0; + } + const btMotionState* motionStateCandidate = collisionCandidate->getMotionState(); + + checkOrAddCollidingState(motionStateCandidate, point, otherPoint); + + return 0; + } +}; + +// Callback for getting colliding avatars in the world. +struct AllAvatarsCallback : public RigidBodyFilterResultCallback { + std::vector intersectingAvatars; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const AvatarMotionState* avatarCandidate = dynamic_cast(otherMotionState); + if (!avatarCandidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingAvatars.emplace_back(avatarCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +// Callback for getting colliding entities in the world. +struct AllEntitiesCallback : public RigidBodyFilterResultCallback { + std::vector intersectingEntities; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const EntityMotionState* entityCandidate = dynamic_cast(otherMotionState); + if (!entityCandidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingEntities.emplace_back(entityCandidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +// TODO: Test if this works. Revert to above code if it doesn't +// Callback for getting colliding ObjectMotionStates in the world, or optionally a more specific type. +template +struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback { + AllObjectMotionStatesCallback(const ShapeInfo& shapeInfo, const Transform& transform) : RigidBodyFilterResultCallback(shapeInfo, transform) { } + + std::vector intersectingObjects; + + void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override { + const T* candidate = dynamic_cast(otherMotionState); + if (!candidate) { + return; + } + + // This is the correct object type. Add it to the list. + intersectingObjects.emplace_back(candidate->getObjectID(), bulletToGLM(point), bulletToGLM(otherPoint)); + } +}; + +#endif // hifi_CollisionPick_h \ No newline at end of file diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 74459ca624..526d099cb4 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -17,6 +17,7 @@ #include "JointRayPick.h" #include "MouseRayPick.h" #include "StylusPick.h" +#include "CollisionPick.h" #include @@ -26,6 +27,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, return createRayPick(properties); case PickQuery::PickType::Stylus: return createStylusPick(properties); + case PickQuery::PickType::Collision: + return createCollisionPick(properties); default: return PickManager::INVALID_PICK_ID; } @@ -134,6 +137,29 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties return DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side, filter, maxDistance, enabled)); } +unsigned int PickScriptingInterface::createCollisionPick(const QVariant& properties) { + QVariantMap propMap = properties.toMap(); + + bool enabled = false; + if (propMap["enabled"].isValid()) { + enabled = propMap["enabled"].toBool(); + } + + PickFilter filter = PickFilter(); + if (propMap["filter"].isValid()) { + filter = PickFilter(propMap["filter"].toUInt()); + } + + float maxDistance = 0.0f; + if (propMap["maxDistance"].isValid()) { + maxDistance = propMap["maxDistance"].toFloat(); + } + + CollisionRegion collisionRegion(propMap); + + return DependencyManager::get()->addPick(PickQuery::Collision, std::make_shared(filter, maxDistance, enabled, collisionRegion, _collisionWorld)); +} + void PickScriptingInterface::enablePick(unsigned int uid) { DependencyManager::get()->enablePick(uid); } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 0ee091716d..48bc6e598e 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -9,6 +9,7 @@ #define hifi_PickScriptingInterface_h #include +#include #include #include @@ -62,6 +63,7 @@ class PickScriptingInterface : public QObject, public Dependency { public: unsigned int createRayPick(const QVariant& properties); unsigned int createStylusPick(const QVariant& properties); + unsigned int createCollisionPick(const QVariant& properties); void registerMetaTypes(QScriptEngine* engine); @@ -273,6 +275,14 @@ public slots: * @returns {number} */ static constexpr unsigned int INTERSECTED_HUD() { return IntersectionType::HUD; } + + // Set to allow CollisionPicks to have access to the physics engine + void setCollisionWorld(const btCollisionWorld* collisionWorld) { + _collisionWorld = collisionWorld; + } + +protected: + const btCollisionWorld* _collisionWorld; }; #endif // hifi_PickScriptingInterface_h diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 2ac195956a..6a491c3791 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -73,6 +73,8 @@ public: const VectorOfMotionStates& getChangedMotionStates(); const VectorOfMotionStates& getDeactivatedMotionStates() const { return _dynamicsWorld->getDeactivatedMotionStates(); } + const ThreadSafeDynamicsWorld* getDynamicsWorld() { return _dynamicsWorld; } + /// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop. const CollisionEvents& getCollisionEvents(); diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 53606b154f..db9302a979 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -161,6 +161,7 @@ public: enum PickType { Ray = 0, Stylus, + Collision, NUM_PICK_TYPES }; diff --git a/libraries/pointers/src/PickManager.cpp b/libraries/pointers/src/PickManager.cpp index ba8fa814f0..b1698a7d1c 100644 --- a/libraries/pointers/src/PickManager.cpp +++ b/libraries/pointers/src/PickManager.cpp @@ -100,6 +100,7 @@ void PickManager::update() { // and the rayPicks updae will ALWAYS update at least one ray even when there is no budget _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD); + _collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false); } bool PickManager::isLeftHand(unsigned int uid) { diff --git a/libraries/pointers/src/PickManager.h b/libraries/pointers/src/PickManager.h index 3b466be2bc..f6871ee9cd 100644 --- a/libraries/pointers/src/PickManager.h +++ b/libraries/pointers/src/PickManager.h @@ -59,12 +59,13 @@ protected: std::shared_ptr findPick(unsigned int uid) const; std::unordered_map>> _picks; - unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 }; + unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 }; std::unordered_map _typeMap; unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; PickCacheOptimizer _stylusPickCacheOptimizer; + PickCacheOptimizer _collisionPickCacheOptimizer; static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC; unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET }; diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index 467d6374a5..d20a993117 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -20,8 +20,10 @@ #include #include "AACube.h" +#include "ShapeInfo.h" #include "SharedUtil.h" #include "shared/Bilateral.h" +#include "Transform.h" class QColor; class QUrl; @@ -219,6 +221,80 @@ public: } }; +class CollisionRegion : public MathPick { +public: + CollisionRegion() { } + CollisionRegion(const QVariantMap& pickVariant) { + if (pickVariant["shape"].isValid()) { + auto shape = pickVariant["shape"].toMap(); + if (!shape.empty()) { + ShapeType shapeType = SHAPE_TYPE_NONE; + if (shape["shapeType"].isValid()) { + shapeType = ShapeInfo::getShapeTypeForName(shape["shapeType"].toString()); + } + if (shapeType >= SHAPE_TYPE_COMPOUND && shapeType <= SHAPE_TYPE_STATIC_MESH && shape["modelURL"].isValid()) { + QString newURL = shape["modelURL"].toString(); + modelURL.setUrl(newURL); + } + else { + modelURL.setUrl(""); + } + + if (shape["dimensions"].isValid()) { + transform.setScale(vec3FromVariant(shape["dimensions"])); + } + } + } + + if (pickVariant["position"].isValid()) { + transform.setTranslation(vec3FromVariant(pickVariant["position"])); + } + if (pickVariant["orientation"].isValid()) { + transform.setRotation(quatFromVariant(pickVariant["orientation"])); + } + } + + QVariantMap toVariantMap() const override { + QVariantMap collisionRegion; + + QVariantMap shape; + shape["shapeType"] = ShapeInfo::getNameForShapeType(shapeInfo.getType()); + shape["modelURL"] = modelURL.toString(); + shape["dimensions"] = vec3toVariant(shapeInfo.getHalfExtents()); + + collisionRegion["shape"] = shape; + + collisionRegion["position"] = vec3toVariant(transform.getTranslation()); + collisionRegion["orientation"] = quatToVariant(transform.getRotation()); + + return collisionRegion; + } + + operator bool() const override { + return !(glm::any(glm::isnan(transform.getTranslation())) || + glm::any(glm::isnan(transform.getRotation())) || + shapeInfo.getType() == SHAPE_TYPE_NONE); + } + + bool shouldComputeShapeInfo() const { + if (!(shapeInfo.getType() == SHAPE_TYPE_HULL || + (shapeInfo.getType() >= SHAPE_TYPE_COMPOUND && + shapeInfo.getType() <= SHAPE_TYPE_STATIC_MESH) + )) { + return false; + } + + return !shapeInfo.getPointCollection().size(); + } + + // We can't load the model here because it would create a circular dependency, so we delegate that responsibility to the owning CollisionPick + QUrl modelURL; + + // We can't compute the shapeInfo here without loading the model first, so we delegate that responsibility to the owning CollisionPick + ShapeInfo shapeInfo; + Transform transform; +}; + namespace std { inline void hash_combine(std::size_t& seed) { } @@ -255,6 +331,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const Transform& a) const { + size_t result = 0; + hash_combine(result, a.getTranslation(), a.getRotation(), a.getScale()); + return result; + } + }; + template <> struct hash { size_t operator()(const PickRay& a) const { @@ -273,6 +358,15 @@ namespace std { } }; + template <> + struct hash { + size_t operator()(const CollisionRegion& a) const { + size_t result = 0; + hash_combine(result, a.transform, (int)a.shapeInfo.getType(), qHash(a.modelURL)); + return result; + } + }; + template <> struct hash { size_t operator()(const QString& a) const { diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index 968292da87..b0caf2d62d 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -74,6 +74,17 @@ QString ShapeInfo::getNameForShapeType(ShapeType type) { return shapeTypeNames[(int)type]; } +ShapeType ShapeInfo::getShapeTypeForName(QString string) { + for (int i = 0; i < SHAPETYPE_NAME_COUNT; i++) { + auto name = shapeTypeNames[i]; + if (name == string) { + return (ShapeType)i; + } + } + + return (ShapeType)0; +} + void ShapeInfo::clear() { _url.clear(); _pointCollection.clear(); diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 069241e29d..5020e492cf 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -59,6 +59,7 @@ public: using TriangleIndices = QVector; static QString getNameForShapeType(ShapeType type); + static ShapeType getShapeTypeForName(QString string); void clear();