Revision control

Copy as Markdown

// Copyright (C) 2016 Klaralvdalens Datakonsult AB (KDAB).
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "pickboundingvolumeutils_p.h"
#include <Qt3DRender/private/geometryrenderer_p.h>
#include <Qt3DRender/private/framegraphnode_p.h>
#include <Qt3DRender/private/cameralens_p.h>
#include <Qt3DRender/private/cameraselectornode_p.h>
#include <Qt3DRender/private/viewportnode_p.h>
#include <Qt3DRender/private/rendersurfaceselector_p.h>
#include <Qt3DRender/private/triangleboundingvolume_p.h>
#include <Qt3DRender/private/nodemanagers_p.h>
#include <Qt3DRender/private/sphere_p.h>
#include <Qt3DRender/private/entity_p.h>
#include <Qt3DRender/private/trianglesvisitor_p.h>
#include <Qt3DRender/private/segmentsvisitor_p.h>
#include <Qt3DRender/private/pointsvisitor_p.h>
#include <Qt3DRender/private/layer_p.h>
#include <Qt3DRender/private/layerfilternode_p.h>
#include <Qt3DRender/private/rendersettings_p.h>
#include <Qt3DRender/private/filterlayerentityjob_p.h>
#include <vector>
#include <algorithm>
#include <functional>
QT_BEGIN_NAMESPACE
namespace Qt3DRender {
using namespace Qt3DRender::RayCasting;
namespace Render {
namespace PickingUtils {
PickConfiguration::PickConfiguration(FrameGraphNode *frameGraphRoot, RenderSettings *renderSettings)
{
ViewportCameraAreaGatherer vcaGatherer;
// TO DO: We could cache this and only gather when we know the FrameGraph tree has changed
vcaDetails = vcaGatherer.gather(frameGraphRoot);
// If we have no viewport / camera or area, return early
if (vcaDetails.empty())
return;
// TO DO:
// If we have move or hover move events that someone cares about, we try to avoid expensive computations
// by compressing them into a single one
trianglePickingRequested = (renderSettings->pickMethod() & QPickingSettings::TrianglePicking);
edgePickingRequested = (renderSettings->pickMethod() & QPickingSettings::LinePicking);
pointPickingRequested = (renderSettings->pickMethod() & QPickingSettings::PointPicking);
primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested;
frontFaceRequested = renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace;
backFaceRequested = renderSettings->faceOrientationPickingMode() != QPickingSettings::FrontFace;
pickWorldSpaceTolerance = renderSettings->pickWorldSpaceTolerance();
}
void ViewportCameraAreaGatherer::visit(FrameGraphNode *node)
{
const auto children = node->children();
for (Render::FrameGraphNode *n : children)
visit(n);
if (node->childrenIds().empty())
m_leaves.push_back(node);
}
ViewportCameraAreaDetails ViewportCameraAreaGatherer::gatherUpViewportCameraAreas(Render::FrameGraphNode *node) const
{
ViewportCameraAreaDetails vca;
vca.viewport = QRectF(0., 0., 1., 1.);
while (node) {
if (node->isEnabled()) {
switch (node->nodeType()) {
case FrameGraphNode::CameraSelector:
vca.cameraId = static_cast<const CameraSelector *>(node)->cameraUuid();
break;
case FrameGraphNode::Viewport: {
auto vnode = static_cast<const ViewportNode *>(node);
// we want the leaf viewport so if we have a viewport node already don't override it with its parent
if (!vca.viewportNodeId)
vca.viewportNodeId = vnode->peerId();
vca.viewport = ViewportNode::computeViewport(vca.viewport, vnode);
break;
}
case FrameGraphNode::Surface: {
auto selector = static_cast<const RenderSurfaceSelector *>(node);
vca.area = selector->renderTargetSize();
vca.surface = selector->surface();
break;
}
case FrameGraphNode::NoPicking: {
// Return an empty/invalid ViewportCameraAreaDetails which will
// prevent picking in the presence of a NoPicking node
return {};
}
case FrameGraphNode::LayerFilter: {
auto fnode = static_cast<const LayerFilterNode *>(node);
vca.layersFilters.push_back(fnode->peerId());
break;
}
default:
break;
}
}
node = node->parent();
}
return vca;
}
std::vector<ViewportCameraAreaDetails> ViewportCameraAreaGatherer::gather(FrameGraphNode *root)
{
// Retrieve all leaves
visit(root);
std::vector<ViewportCameraAreaDetails> vcaTriplets;
vcaTriplets.reserve(m_leaves.size());
// Find all viewport/camera pairs by traversing from leaf to root
for (Render::FrameGraphNode *leaf : m_leaves) {
ViewportCameraAreaDetails vcaDetails = gatherUpViewportCameraAreas(leaf);
if (!m_targetCamera.isNull() && vcaDetails.cameraId != m_targetCamera)
continue;
if (!vcaDetails.cameraId.isNull() && isUnique(vcaTriplets, vcaDetails))
vcaTriplets.push_back(vcaDetails);
}
return vcaTriplets;
}
bool ViewportCameraAreaGatherer::isUnique(const std::vector<ViewportCameraAreaDetails> &vcaList,
const ViewportCameraAreaDetails &vca) const
{
for (const ViewportCameraAreaDetails &listItem : vcaList) {
if (vca.cameraId == listItem.cameraId &&
vca.viewport == listItem.viewport &&
vca.surface == listItem.surface &&
vca.area == listItem.area &&
vca.layersFilters == listItem.layersFilters)
return false;
}
return true;
}
class TriangleCollisionVisitor : public TrianglesVisitor
{
public:
HitList hits;
TriangleCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
bool frontFaceRequested, bool backFaceRequested)
: TrianglesVisitor(manager), m_root(root), m_ray(ray), m_triangleIndex(0)
, m_frontFaceRequested(frontFaceRequested), m_backFaceRequested(backFaceRequested)
{
m_worldMatrix = *m_root->worldTransform();
m_localRay = m_ray;
m_localRay.transform(m_worldMatrix.inverted());
}
private:
const Entity *m_root;
RayCasting::QRay3D m_ray;
RayCasting::QRay3D m_localRay;
Matrix4x4 m_worldMatrix;
uint m_triangleIndex;
bool m_frontFaceRequested;
bool m_backFaceRequested;
void visit(uint andx, const Vector3D &a,
uint bndx, const Vector3D &b,
uint cndx, const Vector3D &c) override;
bool intersectsSegmentTriangle(uint andx, const Vector3D &a,
uint bndx, const Vector3D &b,
uint cndx, const Vector3D &c);
};
void TriangleCollisionVisitor::visit(uint andx, const Vector3D &a, uint bndx, const Vector3D &b, uint cndx, const Vector3D &c)
{
bool intersected = m_frontFaceRequested && intersectsSegmentTriangle(cndx, c, bndx, b, andx, a); // front facing
if (!intersected && m_backFaceRequested) {
intersected = intersectsSegmentTriangle(andx, a, bndx, b, cndx, c); // back facing
}
m_triangleIndex++;
}
bool TriangleCollisionVisitor::intersectsSegmentTriangle(uint andx, const Vector3D &a, uint bndx, const Vector3D &b, uint cndx, const Vector3D &c)
{
float t = 0.0f;
Vector3D uvw;
bool intersected = Render::intersectsSegmentTriangle(m_localRay, a, b, c, uvw, t);
if (intersected) {
QCollisionQueryResult::Hit queryResult;
queryResult.m_type = QCollisionQueryResult::Hit::Triangle;
queryResult.m_entityId = m_root->peerId();
queryResult.m_primitiveIndex = m_triangleIndex;
queryResult.m_vertexIndex[0] = andx;
queryResult.m_vertexIndex[1] = bndx;
queryResult.m_vertexIndex[2] = cndx;
queryResult.m_uvw = uvw;
queryResult.m_intersection = m_worldMatrix.map(m_localRay.point(t * m_localRay.distance()));
queryResult.m_distance = m_ray.projectedDistance(queryResult.m_intersection);
hits.push_back(queryResult);
}
return intersected;
}
class LineCollisionVisitor : public SegmentsVisitor
{
public:
HitList hits;
LineCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
float pickWorldSpaceTolerance)
: SegmentsVisitor(manager), m_root(root), m_ray(ray)
, m_segmentIndex(0), m_pickWorldSpaceTolerance(pickWorldSpaceTolerance)
{
}
private:
const Entity *m_root;
RayCasting::QRay3D m_ray;
uint m_segmentIndex;
float m_pickWorldSpaceTolerance;
void visit(uint andx, const Vector3D &a,
uint bndx, const Vector3D &b) override;
bool intersectsSegmentSegment(uint andx, const Vector3D &a,
uint bndx, const Vector3D &b);
bool rayToLineSegment(const Vector3D& lineStart,const Vector3D& lineEnd,
float &distance, Vector3D &intersection) const;
};
void LineCollisionVisitor::visit(uint andx, const Vector3D &a, uint bndx, const Vector3D &b)
{
const Matrix4x4 &mat = *m_root->worldTransform();
const Vector3D tA = mat.map(a);
const Vector3D tB = mat.map(b);
intersectsSegmentSegment(andx, tA, bndx, tB);
m_segmentIndex++;
}
bool LineCollisionVisitor::intersectsSegmentSegment(uint andx, const Vector3D &a,
uint bndx, const Vector3D &b)
{
float distance = 0.f;
Vector3D intersection;
bool res = rayToLineSegment(a, b, distance, intersection);
if (res) {
QCollisionQueryResult::Hit queryResult;
queryResult.m_type = QCollisionQueryResult::Hit::Edge;
queryResult.m_entityId = m_root->peerId();
queryResult.m_primitiveIndex = m_segmentIndex;
queryResult.m_vertexIndex[0] = andx;
queryResult.m_vertexIndex[1] = bndx;
queryResult.m_intersection = intersection;
queryResult.m_distance = m_ray.projectedDistance(queryResult.m_intersection);
hits.push_back(queryResult);
return true;
}
return false;
}
bool LineCollisionVisitor::rayToLineSegment(const Vector3D& lineStart,const Vector3D& lineEnd,
float &distance, Vector3D &intersection) const
{
const float epsilon = 0.00000001f;
const Vector3D u = m_ray.direction() * m_ray.distance();
const Vector3D v = lineEnd - lineStart;
const Vector3D w = m_ray.origin() - lineStart;
const float a = Vector3D::dotProduct(u, u);
const float b = Vector3D::dotProduct(u, v);
const float c = Vector3D::dotProduct(v, v);
const float d = Vector3D::dotProduct(u, w);
const float e = Vector3D::dotProduct(v, w);
const float D = a * c - b * b;
float sc, sN, sD = D;
float tc, tN, tD = D;
if (D < epsilon) {
sN = 0.0;
sD = 1.0;
tN = e;
tD = c;
} else {
sN = (b * e - c * d);
tN = (a * e - b * d);
if (sN < 0.0) {
sN = 0.0;
tN = e;
tD = c;
}
}
if (tN < 0.0) {
tN = 0.0;
if (-d < 0.0)
sN = 0.0;
else {
sN = -d;
sD = a;
}
} else if (tN > tD) {
tN = tD;
if ((-d + b) < 0.0)
sN = 0;
else {
sN = (-d + b);
sD = a;
}
}
sc = (qAbs(sN) < epsilon ? 0.0f : sN / sD);
tc = (qAbs(tN) < epsilon ? 0.0f : tN / tD);
const Vector3D dP = w + (sc * u) - (tc * v);
const float f = dP.length();
if (f < m_pickWorldSpaceTolerance) {
distance = sc * u.length();
intersection = lineStart + v * tc;
return true;
}
return false;
}
class PointCollisionVisitor : public PointsVisitor
{
public:
HitList hits;
PointCollisionVisitor(NodeManagers* manager, const Entity *root, const RayCasting::QRay3D& ray,
float pickWorldSpaceTolerance)
: PointsVisitor(manager), m_root(root), m_ray(ray)
, m_pointIndex(0), m_pickWorldSpaceTolerance(pickWorldSpaceTolerance)
{
}
private:
const Entity *m_root;
RayCasting::QRay3D m_ray;
uint m_pointIndex;
float m_pickWorldSpaceTolerance;
void visit(uint ndx, const Vector3D &p) override;
double pointToRayDistance(const Vector3D &a, Vector3D &p)
{
const Vector3D v = a - m_ray.origin();
const double t = Vector3D::dotProduct(v, m_ray.direction());
p = m_ray.origin() + t * m_ray.direction();
return (p - a).length();
}
};
void PointCollisionVisitor::visit(uint ndx, const Vector3D &p)
{
const Matrix4x4 &mat = *m_root->worldTransform();
const Vector3D tP = mat.map(p);
Vector3D intersection;
float d = pointToRayDistance(tP, intersection);
if (d < m_pickWorldSpaceTolerance) {
QCollisionQueryResult::Hit queryResult;
queryResult.m_type = QCollisionQueryResult::Hit::Point;
queryResult.m_entityId = m_root->peerId();
queryResult.m_primitiveIndex = m_pointIndex;
queryResult.m_vertexIndex[0] = ndx;
queryResult.m_intersection = intersection;
queryResult.m_distance = d;
hits.push_back(queryResult);
}
m_pointIndex++;
}
HitList reduceToFirstHit(HitList &result, const HitList &intermediate)
{
if (!intermediate.empty()) {
if (result.empty())
result.push_back(intermediate.front());
float closest = result.front().m_distance;
for (const auto &v : intermediate) {
if (v.m_distance < closest) {
result.insert(result.begin(), v);
closest = v.m_distance;
}
}
while (result.size() > 1)
result.pop_back();
}
return result;
}
struct HighestPriorityHitReducer
{
// No need to protect this from concurrent access as the table
// is read only
const QHash<Qt3DCore::QNodeId, int> entityToPriorityTable;
HitList operator()(HitList &result, const HitList &intermediate)
{
// Sort by priority first
// If we have equal priorities, we then sort by distance
if (!intermediate.empty()) {
if (result.empty())
result.push_back(intermediate.front());
int currentPriority = entityToPriorityTable.value(result.front().m_entityId, 0);
float closest = result.front().m_distance;
for (const auto &v : intermediate) {
const int newEntryPriority = entityToPriorityTable.value(v.m_entityId, 0);
if (newEntryPriority > currentPriority) {
result.insert(result.begin(), v);
currentPriority = newEntryPriority;
closest = v.m_distance;
} else if (newEntryPriority == currentPriority) {
if (v.m_distance < closest) {
result.insert(result.begin(), v);
closest = v.m_distance;
currentPriority = newEntryPriority;
}
}
}
while (result.size() > 1)
result.pop_back();
}
return result;
}
};
HitList reduceToAllHits(HitList &results, const HitList &intermediate)
{
if (!intermediate.empty())
results.insert(results.end(), intermediate.begin(), intermediate.end());
return results;
}
AbstractCollisionGathererFunctor::AbstractCollisionGathererFunctor()
: m_manager(nullptr)
{ }
AbstractCollisionGathererFunctor::~AbstractCollisionGathererFunctor()
{ }
HitList AbstractCollisionGathererFunctor::operator ()(const Entity *entity) const
{
if (m_objectPickersRequired) {
HObjectPicker objectPickerHandle = entity->componentHandle<ObjectPicker>();
// If the Entity which actually received the hit doesn't have
// an object picker component, we need to check the parent if it has one ...
auto parentEntity = entity;
while (objectPickerHandle.isNull() && parentEntity != nullptr) {
parentEntity = parentEntity->parent();
if (parentEntity != nullptr)
objectPickerHandle = parentEntity->componentHandle<ObjectPicker>();
}
ObjectPicker *objectPicker = m_manager->objectPickerManager()->data(objectPickerHandle);
if (objectPicker == nullptr || !objectPicker->isEnabled())
return {}; // don't bother picking entities that don't
// have an object picker, or if it's disabled
}
return pick(entity);
}
bool AbstractCollisionGathererFunctor::rayHitsEntity(const Entity *entity) const
{
QRayCastingService rayCasting;
const QCollisionQueryResult::Hit queryResult = rayCasting.query(m_ray, entity->worldBoundingVolume());
return queryResult.m_distance >= 0.f;
}
void AbstractCollisionGathererFunctor::sortHits(HitList &results)
{
auto compareHitsDistance = [](const HitList::value_type &a,
const HitList::value_type &b) {
return a.m_distance < b.m_distance;
};
std::sort(results.begin(), results.end(), compareHitsDistance);
}
namespace {
// Workaround to avoid passing *this into the blockMappedReduce calls for the
// mapFunctor which would cause an SSE alignment error on Windows Also note
// that a lambda doesn't work since we need the typedef result_type defined to
// work with QtConcurrent
struct MapFunctorHolder
{
MapFunctorHolder(const AbstractCollisionGathererFunctor *gatherer)
: m_gatherer(gatherer)
{}
// This define is required to work with QtConcurrent
typedef HitList result_type;
HitList operator ()(const Entity *e) const { return m_gatherer->operator ()(e); }
const AbstractCollisionGathererFunctor *m_gatherer;
};
} // anonymous
HitList EntityCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
Qt3DRender::QPickingSettings::PickResultMode mode)
{
std::function<HitList (HitList &, const HitList &)> reducerOp;
switch (mode) {
case QPickingSettings::AllPicks:
reducerOp = PickingUtils::reduceToAllHits;
break;
case QPickingSettings::NearestPriorityPick:
reducerOp = HighestPriorityHitReducer{ m_entityToPriorityTable };
break;
case QPickingSettings::NearestPick:
reducerOp = PickingUtils::reduceToFirstHit;
break;
}
const MapFunctorHolder holder(this);
#if QT_CONFIG(concurrent)
return QtConcurrent::blockingMappedReduced<HitList>(entities, holder, reducerOp);
#else
HitList sphereHits;
QList<PickingUtils::EntityCollisionGathererFunctor::result_type> results;
for (const Entity *entity : entities)
sphereHits = reducerOp(sphereHits, holder(entity));
return sphereHits;
#endif
}
HitList EntityCollisionGathererFunctor::pick(const Entity *entity) const
{
HitList result;
QRayCastingService rayCasting;
const QCollisionQueryResult::Hit queryResult = rayCasting.query(m_ray, entity->worldBoundingVolume());
if (queryResult.m_distance >= 0.f)
result.push_back(queryResult);
return result;
}
HitList TriangleCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
Qt3DRender::QPickingSettings::PickResultMode mode)
{
std::function<HitList (HitList &, const HitList &)> reducerOp;
switch (mode) {
case QPickingSettings::AllPicks:
reducerOp = PickingUtils::reduceToAllHits;
break;
case QPickingSettings::NearestPriorityPick:
reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable };
break;
case QPickingSettings::NearestPick:
reducerOp = PickingUtils::reduceToFirstHit;
break;
}
const MapFunctorHolder holder(this);
#if QT_CONFIG(concurrent)
return QtConcurrent::blockingMappedReduced<HitList>(entities, holder, reducerOp);
#else
HitList sphereHits;
QList<PickingUtils::TriangleCollisionGathererFunctor::result_type> results;
for (const Entity *entity : entities)
sphereHits = reducerOp(sphereHits, holder(entity));
return sphereHits;
#endif
}
HitList TriangleCollisionGathererFunctor::pick(const Entity *entity) const
{
HitList result;
PickingProxy *proxy = entity->renderComponent<PickingProxy>();
if (proxy && proxy->isEnabled() && proxy->isValid()) {
if (rayHitsEntity(entity)) {
TriangleCollisionVisitor visitor(m_manager, entity, m_ray, m_frontFaceRequested, m_backFaceRequested);
visitor.apply(proxy, entity->peerId());
result = visitor.hits;
sortHits(result);
}
} else {
GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
if (!gRenderer || !gRenderer->isEnabled())
return result;
if (rayHitsEntity(entity)) {
TriangleCollisionVisitor visitor(m_manager, entity, m_ray, m_frontFaceRequested, m_backFaceRequested);
visitor.apply(gRenderer, entity->peerId());
result = visitor.hits;
sortHits(result);
}
}
return result;
}
HitList LineCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
Qt3DRender::QPickingSettings::PickResultMode mode)
{
std::function<HitList (HitList &, const HitList &)> reducerOp;
switch (mode) {
case QPickingSettings::AllPicks:
reducerOp = PickingUtils::reduceToAllHits;
break;
case QPickingSettings::NearestPriorityPick:
reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable };
break;
case QPickingSettings::NearestPick:
reducerOp = PickingUtils::reduceToFirstHit;
break;
}
const MapFunctorHolder holder(this);
#if QT_CONFIG(concurrent)
return QtConcurrent::blockingMappedReduced<HitList>(entities, holder, reducerOp);
#else
HitList sphereHits;
QList<PickingUtils::LineCollisionGathererFunctor::result_type> results;
for (const Entity *entity : entities)
sphereHits = reducerOp(sphereHits, holder(entity));
return sphereHits;
#endif
}
HitList LineCollisionGathererFunctor::pick(const Entity *entity) const
{
HitList result;
PickingProxy *proxy = entity->renderComponent<PickingProxy>();
if (proxy && proxy->isEnabled() && proxy->isValid()) {
if (rayHitsEntity(entity)) {
LineCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
visitor.apply(proxy, entity->peerId());
result = visitor.hits;
sortHits(result);
}
} else {
GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
if (!gRenderer)
return result;
if (rayHitsEntity(entity)) {
LineCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
visitor.apply(gRenderer, entity->peerId());
result = visitor.hits;
sortHits(result);
}
}
return result;
}
HitList PointCollisionGathererFunctor::computeHits(const std::vector<Entity *> &entities,
Qt3DRender::QPickingSettings::PickResultMode mode)
{
std::function<HitList (HitList &, const HitList &)> reducerOp;
switch (mode) {
case QPickingSettings::AllPicks:
reducerOp = PickingUtils::reduceToAllHits;
break;
case QPickingSettings::NearestPriorityPick:
reducerOp = HighestPriorityHitReducer { m_entityToPriorityTable };
break;
case QPickingSettings::NearestPick:
reducerOp = PickingUtils::reduceToFirstHit;
break;
}
const MapFunctorHolder holder(this);
#if QT_CONFIG(concurrent)
return QtConcurrent::blockingMappedReduced<HitList>(entities, holder, reducerOp);
#else
HitList sphereHits;
QList<PickingUtils::PointCollisionGathererFunctor::result_type> results;
for (const Entity *entity : entities)
sphereHits = reducerOp(sphereHits, holder(entity));
return sphereHits;
#endif
}
HitList PointCollisionGathererFunctor::pick(const Entity *entity) const
{
HitList result;
PickingProxy *proxy = entity->renderComponent<PickingProxy>();
if (proxy && proxy->isEnabled() && proxy->isValid() && proxy->primitiveType() != Qt3DCore::QGeometryView::Points) {
if (rayHitsEntity(entity)) {
PointCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
visitor.apply(proxy, entity->peerId());
result = visitor.hits;
sortHits(result);
}
} else {
GeometryRenderer *gRenderer = entity->renderComponent<GeometryRenderer>();
if (!gRenderer)
return result;
if (gRenderer->primitiveType() != Qt3DRender::QGeometryRenderer::Points)
return result;
if (rayHitsEntity(entity)) {
PointCollisionVisitor visitor(m_manager, entity, m_ray, m_pickWorldSpaceTolerance);
visitor.apply(gRenderer, entity->peerId());
result = visitor.hits;
sortHits(result);
}
}
return result;
}
HierarchicalEntityPicker::HierarchicalEntityPicker(const QRay3D &ray, bool requireObjectPicker)
: m_ray(ray)
, m_objectPickersRequired(requireObjectPicker)
{
}
void HierarchicalEntityPicker::setLayerFilterIds(const Qt3DCore::QNodeIdVector &layerFilterIds)
{
m_layerFilterIds = layerFilterIds;
}
void HierarchicalEntityPicker::setLayerIds(const Qt3DCore::QNodeIdVector &layerIds,
QAbstractRayCaster::FilterMode mode)
{
m_layerIds = layerIds;
m_layerFilterMode = mode;
}
bool HierarchicalEntityPicker::collectHits(NodeManagers *manager, Entity *root)
{
m_hits.clear();
m_entities.clear();
m_entityToPriorityTable.clear();
QRayCastingService rayCasting;
struct EntityData {
Entity* entity;
bool hasObjectPicker;
int priority;
};
std::vector<EntityData> worklist;
worklist.push_back({root, !root->componentHandle<ObjectPicker>().isNull(), 0});
// Record all entities that satisfy layerFiltering. We can then check against
// that to see if a picked Entity also satisfies the layer filtering
// Note: PickBoundingVolumeJob filters against LayerFilter nodes (FG) whereas
// the RayCastingJob filters only against a set of Layers and a filter Mode
const bool hasLayerFilters = !m_layerFilterIds.empty();
const bool hasLayers = !m_layerIds.empty();
const bool hasLayerFiltering = hasLayerFilters || hasLayers;
std::vector<Entity *> layerFilterEntities;
FilterLayerEntityJob layerFilterJob;
layerFilterJob.setManager(manager);
if (hasLayerFilters) {
// Note: we expect UpdateEntityLayersJob was called beforehand to handle layer recursivness
// Filtering against LayerFilters (PickBoundingVolumeJob)
if (!m_layerFilterIds.empty()) {
layerFilterJob.setLayerFilters(m_layerFilterIds);
layerFilterJob.run();
layerFilterEntities = layerFilterJob.filteredEntities();
}
}
while (!worklist.empty()) {
EntityData current = worklist.back();
worklist.pop_back();
// first pick entry sub-scene-graph
QCollisionQueryResult::Hit queryResult =
rayCasting.query(m_ray, current.entity->worldBoundingVolumeWithChildren());
if (queryResult.m_distance < 0.f)
continue;
// if we get a hit, we check again for this specific entity
queryResult = rayCasting.query(m_ray, current.entity->worldBoundingVolume());
// Check Entity is in selected Layers if we have LayerIds or LayerFilterIds
// Note: it's not because a parent doesn't satisfy the layerFiltering that a child might not.
// Therefore we need to keep traversing children in all cases
// Are we filtering against layerIds (RayCastingJob)
if (hasLayers) {
// QLayerFilter::FilterMode and QAbstractRayCaster::FilterMode are the same
layerFilterJob.filterEntityAgainstLayers(current.entity, m_layerIds, static_cast<QLayerFilter::FilterMode>(m_layerFilterMode));
layerFilterEntities = layerFilterJob.filteredEntities();
}
const bool isInLayers = !hasLayerFiltering || Qt3DCore::contains(layerFilterEntities, current.entity);
if (isInLayers && queryResult.m_distance >= 0.f && (current.hasObjectPicker || !m_objectPickersRequired)) {
m_entities.push_back(current.entity);
m_hits.push_back(queryResult);
// Record entry for entity/priority
m_entityToPriorityTable.insert(current.entity->peerId(), current.priority);
}
// and pick children
const auto &childrenHandles = current.entity->childrenHandles();
for (const HEntity &handle : childrenHandles) {
Entity *child = manager->renderNodesManager()->data(handle);
if (child) {
ObjectPicker *childPicker = child->renderComponent<ObjectPicker>();
worklist.push_back({child, current.hasObjectPicker || childPicker,
(childPicker ? childPicker->priority() : current.priority)});
}
}
}
return !m_hits.empty();
}
} // PickingUtils
} // Render
} // Qt3DRender
QT_END_NAMESPACE