mirror of
https://github.com/lubosz/overte.git
synced 2025-04-10 08:57:12 +02:00
Create CollisionPick API
This commit is contained in:
parent
1a599fd818
commit
f33ee55f9e
12 changed files with 690 additions and 2 deletions
|
@ -6654,7 +6654,9 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
|
||||
registerInteractiveWindowMetaType(scriptEngine.data());
|
||||
|
||||
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
|
||||
auto pickScriptingInterface = DependencyManager::get<PickScriptingInterface>();
|
||||
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,
|
||||
|
|
301
interface/src/raypick/CollisionPick.cpp
Normal file
301
interface/src/raypick/CollisionPick.cpp
Normal file
|
@ -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 <QtCore/QDebug>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#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<ModelCache>()->getCollisionGeometryResource(pick.modelURL);
|
||||
}
|
||||
|
||||
if (_cachedResource->isLoaded()) {
|
||||
computeShapeInfo(pick, pick.shapeInfo, _cachedResource);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CollisionPick::computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer<GeometryResource> 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<glm::vec3>());
|
||||
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<glm::vec3> 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<int32_t> 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<CollisionRegion*>(&pick))) {
|
||||
// Cannot compute result
|
||||
return std::make_shared<CollisionPickResult>();
|
||||
}
|
||||
|
||||
auto entityCollisionCallback = AllObjectMotionStatesCallback<EntityMotionState>(pick.shapeInfo, pick.transform);
|
||||
btCollisionWorld* collisionWorld = const_cast<btCollisionWorld*>(_collisionWorld);
|
||||
collisionWorld->contactTest(&entityCollisionCallback.collisionObject, entityCollisionCallback);
|
||||
|
||||
return std::make_shared<CollisionPickResult>(pick, entityCollisionCallback.intersectingObjects, std::vector<CollisionPickResult::EntityIntersection>());
|
||||
}
|
||||
|
||||
PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) {
|
||||
return getDefaultResult(QVariantMap());
|
||||
}
|
||||
|
||||
PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) {
|
||||
if (!isShapeInfoReady(*const_cast<CollisionRegion*>(&pick))) {
|
||||
// Cannot compute result
|
||||
return std::make_shared<CollisionPickResult>();
|
||||
}
|
||||
|
||||
auto avatarCollisionCallback = AllObjectMotionStatesCallback<AvatarMotionState>(pick.shapeInfo, pick.transform);
|
||||
btCollisionWorld* collisionWorld = const_cast<btCollisionWorld*>(_collisionWorld);
|
||||
collisionWorld->contactTest(&avatarCollisionCallback.collisionObject, avatarCollisionCallback);
|
||||
|
||||
return std::make_shared<CollisionPickResult>(pick, std::vector<CollisionPickResult::EntityIntersection>(), avatarCollisionCallback.intersectingObjects);
|
||||
}
|
||||
|
||||
PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) {
|
||||
return getDefaultResult(QVariantMap());
|
||||
}
|
238
interface/src/raypick/CollisionPick.h
Normal file
238
interface/src/raypick/CollisionPick.h
Normal file
|
@ -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 <btBulletDynamicsCommon.h>
|
||||
|
||||
#include <avatar/AvatarMotionState.h>
|
||||
#include <EntityMotionState.h>
|
||||
#include <BulletUtil.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <Pick.h>
|
||||
|
||||
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<EntityIntersection>& intersectingEntities, const std::vector<EntityIntersection>& 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<EntityIntersection> intersectingEntities;
|
||||
std::vector<EntityIntersection> 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<CollisionPickResult> newCollisionResult = std::static_pointer_cast<CollisionPickResult>(*const_cast<PickResultPointer*>(&newRes));
|
||||
// Have to reference the raw pointer to work around strange type conversion errors
|
||||
CollisionPickResult* newCollisionResultRaw = const_cast<CollisionPickResult*>(newCollisionResult.get());
|
||||
|
||||
for (EntityIntersection& intersectingEntity : newCollisionResultRaw->intersectingEntities) {
|
||||
intersectingEntities.push_back(intersectingEntity);
|
||||
}
|
||||
for (EntityIntersection& intersectingAvatar : newCollisionResultRaw->intersectingAvatars) {
|
||||
intersectingAvatars.push_back(intersectingAvatar);
|
||||
}
|
||||
|
||||
return std::make_shared<CollisionPickResult>(*this);
|
||||
}
|
||||
};
|
||||
|
||||
class CollisionPick : public Pick<CollisionRegion> {
|
||||
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<CollisionPickResult>(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<GeometryResource> resource);
|
||||
|
||||
CollisionRegion _mathPick;
|
||||
const btCollisionWorld* _collisionWorld;
|
||||
QSharedPointer<GeometryResource> _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<btCollisionShape*>(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<const btRigidBody*>(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<CollisionPickResult::EntityIntersection> intersectingAvatars;
|
||||
|
||||
void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override {
|
||||
const AvatarMotionState* avatarCandidate = dynamic_cast<const AvatarMotionState*>(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<CollisionPickResult::EntityIntersection> intersectingEntities;
|
||||
|
||||
void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override {
|
||||
const EntityMotionState* entityCandidate = dynamic_cast<const EntityMotionState*>(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 <typename T = ObjectMotionState>
|
||||
struct AllObjectMotionStatesCallback : public RigidBodyFilterResultCallback {
|
||||
AllObjectMotionStatesCallback(const ShapeInfo& shapeInfo, const Transform& transform) : RigidBodyFilterResultCallback(shapeInfo, transform) { }
|
||||
|
||||
std::vector<CollisionPickResult::EntityIntersection> intersectingObjects;
|
||||
|
||||
void checkOrAddCollidingState(const btMotionState* otherMotionState, btVector3& point, btVector3& otherPoint) override {
|
||||
const T* candidate = dynamic_cast<const T*>(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
|
|
@ -17,6 +17,7 @@
|
|||
#include "JointRayPick.h"
|
||||
#include "MouseRayPick.h"
|
||||
#include "StylusPick.h"
|
||||
#include "CollisionPick.h"
|
||||
|
||||
#include <ScriptEngine.h>
|
||||
|
||||
|
@ -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<PickManager>()->addPick(PickQuery::Stylus, std::make_shared<StylusPick>(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<PickManager>()->addPick(PickQuery::Collision, std::make_shared<CollisionPick>(filter, maxDistance, enabled, collisionRegion, _collisionWorld));
|
||||
}
|
||||
|
||||
void PickScriptingInterface::enablePick(unsigned int uid) {
|
||||
DependencyManager::get<PickManager>()->enablePick(uid);
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#define hifi_PickScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <DependencyManager.h>
|
||||
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -161,6 +161,7 @@ public:
|
|||
enum PickType {
|
||||
Ray = 0,
|
||||
Stylus,
|
||||
Collision,
|
||||
|
||||
NUM_PICK_TYPES
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -59,12 +59,13 @@ protected:
|
|||
|
||||
std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
|
||||
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
|
||||
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0 };
|
||||
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 };
|
||||
std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
|
||||
unsigned int _nextPickID { INVALID_PICK_ID + 1 };
|
||||
|
||||
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
|
||||
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
|
||||
PickCacheOptimizer<CollisionRegion> _collisionPickCacheOptimizer;
|
||||
|
||||
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 2 * USECS_PER_MSEC;
|
||||
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };
|
||||
|
|
|
@ -20,8 +20,10 @@
|
|||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#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<Transform> {
|
||||
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<PickRay> {
|
||||
size_t operator()(const PickRay& a) const {
|
||||
|
@ -273,6 +358,15 @@ namespace std {
|
|||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<CollisionRegion> {
|
||||
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<QString> {
|
||||
size_t operator()(const QString& a) const {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
using TriangleIndices = QVector<int32_t>;
|
||||
|
||||
static QString getNameForShapeType(ShapeType type);
|
||||
static ShapeType getShapeTypeForName(QString string);
|
||||
|
||||
void clear();
|
||||
|
||||
|
|
Loading…
Reference in a new issue