mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 03:37:59 +02:00
Merge pull request #13714 from sabrina-shanman/safe-teleport-target
Create CollisionPick API
This commit is contained in:
commit
dce7b6eae6
13 changed files with 776 additions and 6 deletions
|
@ -6708,7 +6708,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
||||||
|
|
||||||
registerInteractiveWindowMetaType(scriptEngine.data());
|
registerInteractiveWindowMetaType(scriptEngine.data());
|
||||||
|
|
||||||
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
|
auto pickScriptingInterface = DependencyManager::get<PickScriptingInterface>();
|
||||||
|
pickScriptingInterface->registerMetaTypes(scriptEngine.data());
|
||||||
|
|
||||||
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
// connect this script engines printedMessage signal to the global ScriptEngines these various messages
|
||||||
connect(scriptEngine.data(), &ScriptEngine::printedMessage,
|
connect(scriptEngine.data(), &ScriptEngine::printedMessage,
|
||||||
|
|
353
interface/src/raypick/CollisionPick.cpp
Normal file
353
interface/src/raypick/CollisionPick.cpp
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
//
|
||||||
|
// 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 "UUIDHasher.h"
|
||||||
|
|
||||||
|
void buildObjectIntersectionsMap(IntersectionType intersectionType, const std::vector<ContactTestResult>& objectIntersections, std::unordered_map<QUuid, QVariantMap>& intersections, std::unordered_map<QUuid, QVariantList>& collisionPointPairs) {
|
||||||
|
for (auto& objectIntersection : objectIntersections) {
|
||||||
|
auto at = intersections.find(objectIntersection.foundID);
|
||||||
|
if (at == intersections.end()) {
|
||||||
|
QVariantMap intersectingObject;
|
||||||
|
intersectingObject["id"] = objectIntersection.foundID;
|
||||||
|
intersectingObject["type"] = intersectionType;
|
||||||
|
intersections[objectIntersection.foundID] = intersectingObject;
|
||||||
|
|
||||||
|
collisionPointPairs[objectIntersection.foundID] = QVariantList();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap collisionPointPair;
|
||||||
|
collisionPointPair["pointOnPick"] = vec3toVariant(objectIntersection.testCollisionPoint);
|
||||||
|
collisionPointPair["pointOnObject"] = vec3toVariant(objectIntersection.foundCollisionPoint);
|
||||||
|
|
||||||
|
collisionPointPairs[objectIntersection.foundID].append(collisionPointPair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap CollisionPickResult::toVariantMap() const {
|
||||||
|
QVariantMap variantMap;
|
||||||
|
|
||||||
|
variantMap["intersects"] = intersects;
|
||||||
|
|
||||||
|
std::unordered_map<QUuid, QVariantMap> intersections;
|
||||||
|
std::unordered_map<QUuid, QVariantList> collisionPointPairs;
|
||||||
|
|
||||||
|
buildObjectIntersectionsMap(ENTITY, entityIntersections, intersections, collisionPointPairs);
|
||||||
|
buildObjectIntersectionsMap(AVATAR, avatarIntersections, intersections, collisionPointPairs);
|
||||||
|
|
||||||
|
QVariantList qIntersectingObjects;
|
||||||
|
for (auto& intersectionKeyVal : intersections) {
|
||||||
|
const QUuid& id = intersectionKeyVal.first;
|
||||||
|
QVariantMap& intersection = intersectionKeyVal.second;
|
||||||
|
|
||||||
|
intersection["collisionContacts"] = collisionPointPairs[id];
|
||||||
|
qIntersectingObjects.append(intersection);
|
||||||
|
}
|
||||||
|
|
||||||
|
variantMap["intersectingObjects"] = qIntersectingObjects;
|
||||||
|
variantMap["loaded"] = (loadState == LOAD_STATE_LOADED);
|
||||||
|
variantMap["collisionRegion"] = pickVariant;
|
||||||
|
|
||||||
|
return variantMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CollisionPick::isShapeInfoReady() {
|
||||||
|
if (_mathPick.shouldComputeShapeInfo()) {
|
||||||
|
if (_cachedResource && _cachedResource->isLoaded()) {
|
||||||
|
computeShapeInfo(_mathPick, *_mathPick.shapeInfo, _cachedResource);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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
|
||||||
|
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((int)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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<ContactTestResult> CollisionPick::filterIntersections(const std::vector<ContactTestResult>& intersections) const {
|
||||||
|
std::vector<ContactTestResult> filteredIntersections;
|
||||||
|
|
||||||
|
const QVector<QUuid>& ignoreItems = getIgnoreItems();
|
||||||
|
const QVector<QUuid>& includeItems = getIncludeItems();
|
||||||
|
bool isWhitelist = includeItems.size();
|
||||||
|
for (const auto& intersection : intersections) {
|
||||||
|
const QUuid& id = intersection.foundID;
|
||||||
|
if (!ignoreItems.contains(id) && (!isWhitelist || includeItems.contains(id))) {
|
||||||
|
filteredIntersections.push_back(intersection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredIntersections;
|
||||||
|
}
|
||||||
|
|
||||||
|
PickResultPointer CollisionPick::getEntityIntersection(const CollisionRegion& pick) {
|
||||||
|
if (!isShapeInfoReady()) {
|
||||||
|
// Cannot compute result
|
||||||
|
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& entityIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_ENTITY, *pick.shapeInfo, pick.transform));
|
||||||
|
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, entityIntersections, std::vector<ContactTestResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
PickResultPointer CollisionPick::getOverlayIntersection(const CollisionRegion& pick) {
|
||||||
|
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
PickResultPointer CollisionPick::getAvatarIntersection(const CollisionRegion& pick) {
|
||||||
|
if (!isShapeInfoReady()) {
|
||||||
|
// Cannot compute result
|
||||||
|
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& avatarIntersections = filterIntersections(_physicsEngine->getCollidingInRegion(MOTIONSTATE_TYPE_AVATAR, *pick.shapeInfo, pick.transform));
|
||||||
|
return std::make_shared<CollisionPickResult>(pick, CollisionPickResult::LOAD_STATE_LOADED, std::vector<ContactTestResult>(), avatarIntersections);
|
||||||
|
}
|
||||||
|
|
||||||
|
PickResultPointer CollisionPick::getHUDIntersection(const CollisionRegion& pick) {
|
||||||
|
return std::make_shared<CollisionPickResult>(pick.toVariantMap(), isShapeInfoReady() ? CollisionPickResult::LOAD_STATE_LOADED : CollisionPickResult::LOAD_STATE_NOT_LOADED, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
|
||||||
|
}
|
102
interface/src/raypick/CollisionPick.h
Normal file
102
interface/src/raypick/CollisionPick.h
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
//
|
||||||
|
// 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 <PhysicsEngine.h>
|
||||||
|
#include <model-networking/ModelCache.h>
|
||||||
|
#include <RegisteredMetaTypes.h>
|
||||||
|
#include <Pick.h>
|
||||||
|
|
||||||
|
class CollisionPickResult : public PickResult {
|
||||||
|
public:
|
||||||
|
enum LoadState {
|
||||||
|
LOAD_STATE_UNKNOWN,
|
||||||
|
LOAD_STATE_NOT_LOADED,
|
||||||
|
LOAD_STATE_LOADED
|
||||||
|
};
|
||||||
|
|
||||||
|
CollisionPickResult() {}
|
||||||
|
CollisionPickResult(const QVariantMap& pickVariant) : PickResult(pickVariant) {}
|
||||||
|
|
||||||
|
CollisionPickResult(const CollisionRegion& searchRegion, LoadState loadState, const std::vector<ContactTestResult>& entityIntersections, const std::vector<ContactTestResult>& avatarIntersections) :
|
||||||
|
PickResult(searchRegion.toVariantMap()),
|
||||||
|
loadState(loadState),
|
||||||
|
intersects(entityIntersections.size() || avatarIntersections.size()),
|
||||||
|
entityIntersections(entityIntersections),
|
||||||
|
avatarIntersections(avatarIntersections) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionPickResult(const CollisionPickResult& collisionPickResult) : PickResult(collisionPickResult.pickVariant) {
|
||||||
|
avatarIntersections = collisionPickResult.avatarIntersections;
|
||||||
|
entityIntersections = collisionPickResult.entityIntersections;
|
||||||
|
intersects = collisionPickResult.intersects;
|
||||||
|
loadState = collisionPickResult.loadState;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadState loadState { LOAD_STATE_UNKNOWN };
|
||||||
|
bool intersects { false };
|
||||||
|
std::vector<ContactTestResult> entityIntersections;
|
||||||
|
std::vector<ContactTestResult> avatarIntersections;
|
||||||
|
|
||||||
|
QVariantMap toVariantMap() const override;
|
||||||
|
|
||||||
|
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>(newRes);
|
||||||
|
|
||||||
|
for (ContactTestResult& entityIntersection : newCollisionResult->entityIntersections) {
|
||||||
|
entityIntersections.push_back(entityIntersection);
|
||||||
|
}
|
||||||
|
for (ContactTestResult& avatarIntersection : newCollisionResult->avatarIntersections) {
|
||||||
|
avatarIntersections.push_back(avatarIntersection);
|
||||||
|
}
|
||||||
|
|
||||||
|
intersects = entityIntersections.size() || avatarIntersections.size();
|
||||||
|
if (newCollisionResult->loadState == LOAD_STATE_NOT_LOADED || loadState == LOAD_STATE_UNKNOWN) {
|
||||||
|
loadState = (LoadState)newCollisionResult->loadState;
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_shared<CollisionPickResult>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class CollisionPick : public Pick<CollisionRegion> {
|
||||||
|
public:
|
||||||
|
CollisionPick(const PickFilter& filter, float maxDistance, bool enabled, CollisionRegion collisionRegion, PhysicsEnginePointer physicsEngine) :
|
||||||
|
Pick(filter, maxDistance, enabled),
|
||||||
|
_mathPick(collisionRegion),
|
||||||
|
_physicsEngine(physicsEngine) {
|
||||||
|
if (collisionRegion.shouldComputeShapeInfo()) {
|
||||||
|
_cachedResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(collisionRegion.modelURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CollisionRegion getMathematicalPick() const override;
|
||||||
|
PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override {
|
||||||
|
return std::make_shared<CollisionPickResult>(pickVariant, CollisionPickResult::LOAD_STATE_UNKNOWN, std::vector<ContactTestResult>(), std::vector<ContactTestResult>());
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
void computeShapeInfo(CollisionRegion& pick, ShapeInfo& shapeInfo, QSharedPointer<GeometryResource> resource);
|
||||||
|
const std::vector<ContactTestResult> filterIntersections(const std::vector<ContactTestResult>& intersections) const;
|
||||||
|
|
||||||
|
CollisionRegion _mathPick;
|
||||||
|
PhysicsEnginePointer _physicsEngine;
|
||||||
|
QSharedPointer<GeometryResource> _cachedResource;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_CollisionPick_h
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
#include "GLMHelpers.h"
|
#include "GLMHelpers.h"
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
#include <PickManager.h>
|
#include <PickManager.h>
|
||||||
|
|
||||||
#include "StaticRayPick.h"
|
#include "StaticRayPick.h"
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
#include "StaticParabolaPick.h"
|
#include "StaticParabolaPick.h"
|
||||||
#include "JointParabolaPick.h"
|
#include "JointParabolaPick.h"
|
||||||
#include "MouseParabolaPick.h"
|
#include "MouseParabolaPick.h"
|
||||||
|
#include "CollisionPick.h"
|
||||||
|
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
|
|
||||||
|
@ -31,6 +33,8 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
|
||||||
return createStylusPick(properties);
|
return createStylusPick(properties);
|
||||||
case PickQuery::PickType::Parabola:
|
case PickQuery::PickType::Parabola:
|
||||||
return createParabolaPick(properties);
|
return createParabolaPick(properties);
|
||||||
|
case PickQuery::PickType::Collision:
|
||||||
|
return createCollisionPick(properties);
|
||||||
default:
|
default:
|
||||||
return PickManager::INVALID_PICK_ID;
|
return PickManager::INVALID_PICK_ID;
|
||||||
}
|
}
|
||||||
|
@ -234,6 +238,48 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
|
||||||
return PickManager::INVALID_PICK_ID;
|
return PickManager::INVALID_PICK_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* A Shape defines a physical volume.
|
||||||
|
*
|
||||||
|
* @typedef {object} Shape
|
||||||
|
* @property {string} shapeType The type of shape to use. Can be one of the following: "box", "sphere", "capsule-x", "capsule-y", "capsule-z", "cylinder-x", "cylinder-y", "cylinder-z"
|
||||||
|
* @property {Vec3} dimensions - The size to scale the shape to.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Add this property to the Shape jsdoc above once model picks work properly
|
||||||
|
// * @property {string} modelURL - If shapeType is one of: "compound", "simple-hull", "simple-compound", or "static-mesh", this defines the model to load to generate the collision volume.
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* A set of properties that can be passed to {@link Picks.createPick} to create a new Collision Pick.
|
||||||
|
|
||||||
|
* @typedef {object} Picks.CollisionPickProperties
|
||||||
|
* @property {Shape} shape - The information about the collision region's size and shape.
|
||||||
|
* @property {Vec3} position - The position of the collision region.
|
||||||
|
* @property {Quat} orientation - The orientation of the collision region.
|
||||||
|
*/
|
||||||
|
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, qApp->getPhysicsEngine()));
|
||||||
|
}
|
||||||
|
|
||||||
void PickScriptingInterface::enablePick(unsigned int uid) {
|
void PickScriptingInterface::enablePick(unsigned int uid) {
|
||||||
DependencyManager::get<PickManager>()->enablePick(uid);
|
DependencyManager::get<PickManager>()->enablePick(uid);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <RegisteredMetaTypes.h>
|
#include <RegisteredMetaTypes.h>
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
|
#include <PhysicsEngine.h>
|
||||||
#include <Pick.h>
|
#include <Pick.h>
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
@ -62,6 +63,7 @@ class PickScriptingInterface : public QObject, public Dependency {
|
||||||
public:
|
public:
|
||||||
unsigned int createRayPick(const QVariant& properties);
|
unsigned int createRayPick(const QVariant& properties);
|
||||||
unsigned int createStylusPick(const QVariant& properties);
|
unsigned int createStylusPick(const QVariant& properties);
|
||||||
|
unsigned int createCollisionPick(const QVariant& properties);
|
||||||
unsigned int createParabolaPick(const QVariant& properties);
|
unsigned int createParabolaPick(const QVariant& properties);
|
||||||
|
|
||||||
void registerMetaTypes(QScriptEngine* engine);
|
void registerMetaTypes(QScriptEngine* engine);
|
||||||
|
@ -72,7 +74,7 @@ public:
|
||||||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
|
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
|
||||||
* @function Picks.createPick
|
* @function Picks.createPick
|
||||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||||
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties|Picks.CollisionPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
||||||
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
|
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
|
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
|
||||||
|
@ -141,11 +143,40 @@ public:
|
||||||
* @property {PickParabola} parabola The PickParabola that was used. Valid even if there was no intersection.
|
* @property {PickParabola} parabola The PickParabola that was used. Valid even if there was no intersection.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* An intersection result for a Collision Pick.
|
||||||
|
*
|
||||||
|
* @typedef {object} CollisionPickResult
|
||||||
|
* @property {boolean} intersects If there was at least one valid intersection (intersectingObjects.length > 0)
|
||||||
|
* @property {IntersectingObject[]} intersectingObjects The collision information of each object which intersect with the CollisionRegion.
|
||||||
|
* @property {CollisionRegion} collisionRegion The CollisionRegion that was used. Valid even if there was no intersection.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Add this to the CollisionPickResult jsdoc once model collision picks are working
|
||||||
|
//* @property {boolean} loaded If the CollisionRegion was successfully loaded (may be false if a model was used)
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Information about the Collision Pick's intersection with an object
|
||||||
|
*
|
||||||
|
* @typedef {object} IntersectingObject
|
||||||
|
* @property {QUuid} id The ID of the object.
|
||||||
|
* @property {number} type The type of the object, either Picks.INTERSECTED_ENTITY() or Picks.INTERSECTED_AVATAR()
|
||||||
|
* @property {CollisionContact[]} collisionContacts Pairs of points representing penetration information between the pick and the object
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* A pair of points that represents part of an overlap between a Collision Pick and an object in the physics engine. Points which are further apart represent deeper overlap
|
||||||
|
*
|
||||||
|
* @typedef {object} CollisionContact
|
||||||
|
* @property {Vec3} pointOnPick A point representing a penetration of the object's surface into the volume of the pick, in world space.
|
||||||
|
* @property {Vec3} pointOnObject A point representing a penetration of the pick's surface into the volume of the found object, in world space.
|
||||||
|
*/
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
|
* Get the most recent pick result from this Pick. This will be updated as long as the Pick is enabled.
|
||||||
* @function Picks.getPrevPickResult
|
* @function Picks.getPrevPickResult
|
||||||
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
* @param {number} uid The ID of the Pick, as returned by {@link Picks.createPick}.
|
||||||
* @returns {RayPickResult|StylusPickResult} The most recent intersection result. This will be different for different PickTypes.
|
* @returns {RayPickResult|StylusPickResult|ParabolaPickResult|CollisionPickResult} The most recent intersection result. This will be different for different PickTypes.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
|
Q_INVOKABLE QVariantMap getPrevPickResult(unsigned int uid);
|
||||||
|
|
||||||
|
|
|
@ -863,3 +863,90 @@ void PhysicsEngine::setShowBulletConstraintLimits(bool value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AllContactsCallback : public btCollisionWorld::ContactResultCallback {
|
||||||
|
AllContactsCallback(MotionStateType desiredObjectType, const ShapeInfo& shapeInfo, const Transform& transform, btCollisionObject* myAvatarCollisionObject) :
|
||||||
|
btCollisionWorld::ContactResultCallback(),
|
||||||
|
desiredObjectType(desiredObjectType),
|
||||||
|
collisionObject(),
|
||||||
|
contacts(),
|
||||||
|
myAvatarCollisionObject(myAvatarCollisionObject) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
~AllContactsCallback() {
|
||||||
|
ObjectMotionState::getShapeManager()->releaseShape(collisionObject.getCollisionShape());
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionStateType desiredObjectType;
|
||||||
|
btCollisionObject collisionObject;
|
||||||
|
std::vector<ContactTestResult> contacts;
|
||||||
|
btCollisionObject* myAvatarCollisionObject;
|
||||||
|
|
||||||
|
bool needsCollision(btBroadphaseProxy* proxy) const override {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0, int partId0, int index0, const btCollisionObjectWrapper* colObj1, int partId1, int index1) override {
|
||||||
|
const btCollisionObject* otherBody;
|
||||||
|
btVector3 penetrationPoint;
|
||||||
|
btVector3 otherPenetrationPoint;
|
||||||
|
if (colObj0->m_collisionObject == &collisionObject) {
|
||||||
|
otherBody = colObj1->m_collisionObject;
|
||||||
|
penetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform());
|
||||||
|
otherPenetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform());
|
||||||
|
} else {
|
||||||
|
otherBody = colObj0->m_collisionObject;
|
||||||
|
penetrationPoint = getWorldPoint(cp.m_localPointA, colObj0->getWorldTransform());
|
||||||
|
otherPenetrationPoint = getWorldPoint(cp.m_localPointB, colObj1->getWorldTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Give MyAvatar a motion state so we don't have to do this
|
||||||
|
if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && myAvatarCollisionObject && myAvatarCollisionObject == otherBody) {
|
||||||
|
contacts.emplace_back(Physics::getSessionUUID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(otherBody->getInternalType() & btCollisionObject::CO_RIGID_BODY)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
const btRigidBody* collisionCandidate = static_cast<const btRigidBody*>(otherBody);
|
||||||
|
|
||||||
|
const btMotionState* motionStateCandidate = collisionCandidate->getMotionState();
|
||||||
|
const ObjectMotionState* candidate = dynamic_cast<const ObjectMotionState*>(motionStateCandidate);
|
||||||
|
if (!candidate || candidate->getType() != desiredObjectType) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is the correct object type. Add it to the list.
|
||||||
|
contacts.emplace_back(candidate->getObjectID(), bulletToGLM(penetrationPoint), bulletToGLM(otherPenetrationPoint));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static btVector3 getWorldPoint(const btVector3& localPoint, const btTransform& transform) {
|
||||||
|
return quatRotate(transform.getRotation(), localPoint) + transform.getOrigin();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::vector<ContactTestResult> PhysicsEngine::getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const {
|
||||||
|
// TODO: Give MyAvatar a motion state so we don't have to do this
|
||||||
|
btCollisionObject* myAvatarCollisionObject = nullptr;
|
||||||
|
if (desiredObjectType == MOTIONSTATE_TYPE_AVATAR && _myAvatarController) {
|
||||||
|
myAvatarCollisionObject = _myAvatarController->getCollisionObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto contactCallback = AllContactsCallback(desiredObjectType, regionShapeInfo, regionTransform, myAvatarCollisionObject);
|
||||||
|
_dynamicsWorld->contactTest(&contactCallback.collisionObject, contactCallback);
|
||||||
|
|
||||||
|
return contactCallback.contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,28 @@ public:
|
||||||
void* _b; // ObjectMotionState pointer
|
void* _b; // ObjectMotionState pointer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ContactTestResult {
|
||||||
|
ContactTestResult() = delete;
|
||||||
|
|
||||||
|
ContactTestResult(const ContactTestResult& contactTestResult) :
|
||||||
|
foundID(contactTestResult.foundID),
|
||||||
|
testCollisionPoint(contactTestResult.testCollisionPoint),
|
||||||
|
foundCollisionPoint(contactTestResult.foundCollisionPoint) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactTestResult(QUuid foundID, glm::vec3 testCollisionPoint, glm::vec3 otherCollisionPoint) :
|
||||||
|
foundID(foundID),
|
||||||
|
testCollisionPoint(testCollisionPoint),
|
||||||
|
foundCollisionPoint(otherCollisionPoint) {
|
||||||
|
}
|
||||||
|
|
||||||
|
QUuid foundID;
|
||||||
|
// The deepest point of an intersection within the volume of the test shape, in world space.
|
||||||
|
glm::vec3 testCollisionPoint;
|
||||||
|
// The deepest point of an intersection within the volume of the found object, in world space.
|
||||||
|
glm::vec3 foundCollisionPoint;
|
||||||
|
};
|
||||||
|
|
||||||
using ContactMap = std::map<ContactKey, ContactInfo>;
|
using ContactMap = std::map<ContactKey, ContactInfo>;
|
||||||
using CollisionEvents = std::vector<Collision>;
|
using CollisionEvents = std::vector<Collision>;
|
||||||
|
|
||||||
|
@ -103,6 +125,9 @@ public:
|
||||||
void setShowBulletConstraints(bool value);
|
void setShowBulletConstraints(bool value);
|
||||||
void setShowBulletConstraintLimits(bool value);
|
void setShowBulletConstraintLimits(bool value);
|
||||||
|
|
||||||
|
// Function for getting colliding ObjectMotionStates in the world of specified type
|
||||||
|
const std::vector<ContactTestResult> getCollidingInRegion(MotionStateType desiredObjectType, const ShapeInfo& regionShapeInfo, const Transform& regionTransform) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);
|
QList<EntityDynamicPointer> removeDynamicsForBody(btRigidBody* body);
|
||||||
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
|
void addObjectToDynamicsWorld(ObjectMotionState* motionState);
|
||||||
|
|
|
@ -164,7 +164,7 @@ public:
|
||||||
Ray = 0,
|
Ray = 0,
|
||||||
Stylus,
|
Stylus,
|
||||||
Parabola,
|
Parabola,
|
||||||
|
Collision,
|
||||||
NUM_PICK_TYPES
|
NUM_PICK_TYPES
|
||||||
};
|
};
|
||||||
Q_ENUM(PickType)
|
Q_ENUM(PickType)
|
||||||
|
|
|
@ -101,6 +101,7 @@ void PickManager::update() {
|
||||||
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
|
_stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], _nextPickToUpdate[PickQuery::Stylus], expiry, false);
|
||||||
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
|
_rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], _nextPickToUpdate[PickQuery::Ray], expiry, shouldPickHUD);
|
||||||
_parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD);
|
_parabolaPickCacheOptimizer.update(cachedPicks[PickQuery::Parabola], _nextPickToUpdate[PickQuery::Parabola], expiry, shouldPickHUD);
|
||||||
|
_collisionPickCacheOptimizer.update(cachedPicks[PickQuery::Collision], _nextPickToUpdate[PickQuery::Collision], expiry, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PickManager::isLeftHand(unsigned int uid) {
|
bool PickManager::isLeftHand(unsigned int uid) {
|
||||||
|
|
|
@ -59,13 +59,14 @@ protected:
|
||||||
|
|
||||||
std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
|
std::shared_ptr<PickQuery> findPick(unsigned int uid) const;
|
||||||
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
|
std::unordered_map<PickQuery::PickType, std::unordered_map<unsigned int, std::shared_ptr<PickQuery>>> _picks;
|
||||||
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0 };
|
unsigned int _nextPickToUpdate[PickQuery::NUM_PICK_TYPES] { 0, 0, 0, 0 };
|
||||||
std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
|
std::unordered_map<unsigned int, PickQuery::PickType> _typeMap;
|
||||||
unsigned int _nextPickID { INVALID_PICK_ID + 1 };
|
unsigned int _nextPickID { INVALID_PICK_ID + 1 };
|
||||||
|
|
||||||
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
|
PickCacheOptimizer<PickRay> _rayPickCacheOptimizer;
|
||||||
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
|
PickCacheOptimizer<StylusTip> _stylusPickCacheOptimizer;
|
||||||
PickCacheOptimizer<PickParabola> _parabolaPickCacheOptimizer;
|
PickCacheOptimizer<PickParabola> _parabolaPickCacheOptimizer;
|
||||||
|
PickCacheOptimizer<CollisionRegion> _collisionPickCacheOptimizer;
|
||||||
|
|
||||||
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC;
|
static const unsigned int DEFAULT_PER_FRAME_TIME_BUDGET = 3 * USECS_PER_MSEC;
|
||||||
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };
|
unsigned int _perFrameTimeBudget { DEFAULT_PER_FRAME_TIME_BUDGET };
|
||||||
|
|
|
@ -20,8 +20,10 @@
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
|
||||||
#include "AACube.h"
|
#include "AACube.h"
|
||||||
|
#include "ShapeInfo.h"
|
||||||
#include "SharedUtil.h"
|
#include "SharedUtil.h"
|
||||||
#include "shared/Bilateral.h"
|
#include "shared/Bilateral.h"
|
||||||
|
#include "Transform.h"
|
||||||
|
|
||||||
class QColor;
|
class QColor;
|
||||||
class QUrl;
|
class QUrl;
|
||||||
|
@ -251,6 +253,97 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* A CollisionPick defines a volume for checking collisions in the physics simulation.
|
||||||
|
|
||||||
|
* @typedef {object} CollisionPick
|
||||||
|
* @property {Shape} shape - The information about the collision region's size and shape.
|
||||||
|
* @property {Vec3} position - The position of the collision region.
|
||||||
|
* @property {Quat} orientation - The orientation of the collision region.
|
||||||
|
*/
|
||||||
|
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"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
shapeInfo->setParams(shapeType, transform.getScale() / 2.0f, modelURL.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(transform.getScale());
|
||||||
|
|
||||||
|
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 operator==(const CollisionRegion& other) const {
|
||||||
|
return glm::all(glm::equal(transform.getTranslation(), other.transform.getTranslation())) &&
|
||||||
|
glm::all(glm::equal(transform.getRotation(), other.transform.getRotation())) &&
|
||||||
|
glm::all(glm::equal(transform.getScale(), other.transform.getScale())) &&
|
||||||
|
shapeInfo->getType() == other.shapeInfo->getType() &&
|
||||||
|
modelURL == other.modelURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
std::shared_ptr<ShapeInfo> shapeInfo = std::make_shared<ShapeInfo>();
|
||||||
|
Transform transform;
|
||||||
|
};
|
||||||
|
|
||||||
namespace std {
|
namespace std {
|
||||||
inline void hash_combine(std::size_t& seed) { }
|
inline void hash_combine(std::size_t& seed) { }
|
||||||
|
|
||||||
|
@ -286,6 +379,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 <>
|
template <>
|
||||||
struct hash<PickRay> {
|
struct hash<PickRay> {
|
||||||
size_t operator()(const PickRay& a) const {
|
size_t operator()(const PickRay& a) const {
|
||||||
|
@ -313,6 +415,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 <>
|
template <>
|
||||||
struct hash<QString> {
|
struct hash<QString> {
|
||||||
size_t operator()(const QString& a) const {
|
size_t operator()(const QString& a) const {
|
||||||
|
|
|
@ -68,12 +68,23 @@ const float MIN_HALF_EXTENT = 0.005f; // 0.5 cm
|
||||||
|
|
||||||
QString ShapeInfo::getNameForShapeType(ShapeType type) {
|
QString ShapeInfo::getNameForShapeType(ShapeType type) {
|
||||||
if (((int)type <= 0) || ((int)type >= (int)SHAPETYPE_NAME_COUNT)) {
|
if (((int)type <= 0) || ((int)type >= (int)SHAPETYPE_NAME_COUNT)) {
|
||||||
type = (ShapeType)0;
|
type = SHAPE_TYPE_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return shapeTypeNames[(int)type];
|
return shapeTypeNames[(int)type];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShapeType ShapeInfo::getShapeTypeForName(QString string) {
|
||||||
|
for (int i = 0; i < (int)SHAPETYPE_NAME_COUNT; i++) {
|
||||||
|
auto name = shapeTypeNames[i];
|
||||||
|
if (name == string) {
|
||||||
|
return (ShapeType)i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SHAPE_TYPE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
void ShapeInfo::clear() {
|
void ShapeInfo::clear() {
|
||||||
_url.clear();
|
_url.clear();
|
||||||
_pointCollection.clear();
|
_pointCollection.clear();
|
||||||
|
|
|
@ -59,6 +59,7 @@ public:
|
||||||
using TriangleIndices = QVector<int32_t>;
|
using TriangleIndices = QVector<int32_t>;
|
||||||
|
|
||||||
static QString getNameForShapeType(ShapeType type);
|
static QString getNameForShapeType(ShapeType type);
|
||||||
|
static ShapeType getShapeTypeForName(QString string);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue