| /**************************************************************************** |
| ** |
| ** Copyright (C) 2018 Klaralvdalens Datakonsult AB (KDAB). |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Qt3D module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "raycastingjob_p.h" |
| #include <Qt3DCore/private/qaspectmanager_p.h> |
| #include <Qt3DRender/qgeometryrenderer.h> |
| #include <Qt3DRender/private/entity_p.h> |
| #include <Qt3DRender/private/geometryrenderer_p.h> |
| #include <Qt3DRender/private/job_common_p.h> |
| #include <Qt3DRender/private/managers_p.h> |
| #include <Qt3DRender/private/nodemanagers_p.h> |
| #include <Qt3DRender/private/pickboundingvolumeutils_p.h> |
| #include <Qt3DRender/private/qray3d_p.h> |
| #include <Qt3DRender/private/sphere_p.h> |
| #include <Qt3DRender/private/rendersettings_p.h> |
| #include <Qt3DRender/private/trianglesvisitor_p.h> |
| #include <Qt3DRender/private/entityvisitor_p.h> |
| #include <Qt3DRender/private/qabstractraycaster_p.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| using namespace Qt3DRender; |
| using namespace Qt3DRender::RayCasting; |
| using namespace Render; |
| |
| namespace { |
| |
| class EntityCasterGatherer : public EntityVisitor |
| { |
| public: |
| using EntityCasterList = QVector<QPair<Entity *, RayCaster*>>; |
| EntityCasterList m_result; |
| |
| explicit EntityCasterGatherer(NodeManagers *manager) : EntityVisitor(manager) { setPruneDisabled(true); } |
| |
| Operation visit(Entity *entity) override { |
| QVector<RayCaster *> components = entity->renderComponents<RayCaster>(); |
| for (const auto c: qAsConst(components)) { |
| if (c->isEnabled()) |
| m_result.push_back(qMakePair(entity, c)); |
| } |
| |
| return Continue; |
| } |
| }; |
| |
| } // anonymous |
| |
| |
| class Qt3DRender::Render::RayCastingJobPrivate : public Qt3DCore::QAspectJobPrivate |
| { |
| public: |
| RayCastingJobPrivate(RayCastingJob *q) : q_ptr(q) { } |
| ~RayCastingJobPrivate() override { Q_ASSERT(dispatches.isEmpty()); } |
| |
| bool isRequired() const override; |
| void postFrame(Qt3DCore::QAspectManager *manager) override; |
| |
| QVector<QPair<RayCaster *, QAbstractRayCaster::Hits>> dispatches; |
| |
| RayCastingJob *q_ptr; |
| Q_DECLARE_PUBLIC(RayCastingJob) |
| }; |
| |
| |
| bool RayCastingJobPrivate::isRequired() const |
| { |
| Q_Q(const RayCastingJob); |
| return q->m_castersDirty || q->m_oneEnabledAtLeast; |
| } |
| |
| void RayCastingJobPrivate::postFrame(Qt3DCore::QAspectManager *manager) |
| { |
| for (auto res: qAsConst(dispatches)) { |
| QAbstractRayCaster *node = qobject_cast<QAbstractRayCaster *>(manager->lookupNode(res.first->peerId())); |
| if (!node) |
| continue; |
| |
| QAbstractRayCasterPrivate *d = QAbstractRayCasterPrivate::get(node); |
| d->dispatchHits(res.second); |
| |
| if (node->runMode() == QAbstractRayCaster::SingleShot) { |
| node->setEnabled(false); |
| res.first->setEnabled(false); |
| } |
| } |
| |
| dispatches.clear(); |
| } |
| |
| |
| RayCastingJob::RayCastingJob() |
| : AbstractPickingJob(*new RayCastingJobPrivate(this)) |
| , m_castersDirty(true) |
| { |
| SET_JOB_RUN_STAT_TYPE(this, JobTypes::RayCasting, 0) |
| } |
| |
| void RayCastingJob::markCastersDirty() |
| { |
| m_castersDirty = true; |
| } |
| |
| bool RayCastingJob::runHelper() |
| { |
| // Quickly look which caster settings we've got |
| // NOTE: should not really cached, we're tracking the state of |
| // RayCaster components but not of the parent entities |
| if (m_castersDirty) { |
| m_castersDirty = false; |
| m_oneEnabledAtLeast = false; |
| |
| const auto activeHandles = m_manager->rayCasterManager()->activeHandles(); |
| for (const auto &handle : activeHandles) { |
| const auto caster = m_manager->rayCasterManager()->data(handle); |
| m_oneEnabledAtLeast |= caster->isEnabled(); |
| if (m_oneEnabledAtLeast) |
| break; |
| } |
| } |
| |
| // bail out early if no caster is enabled |
| if (!m_oneEnabledAtLeast) |
| return false; |
| |
| const bool trianglePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::TrianglePicking); |
| const bool edgePickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::LinePicking); |
| const bool pointPickingRequested = (m_renderSettings->pickMethod() & QPickingSettings::PointPicking); |
| const bool primitivePickingRequested = pointPickingRequested | edgePickingRequested | trianglePickingRequested; |
| const bool frontFaceRequested = |
| m_renderSettings->faceOrientationPickingMode() != QPickingSettings::BackFace; |
| const bool backFaceRequested = |
| m_renderSettings->faceOrientationPickingMode() != QPickingSettings::FrontFace; |
| const float pickWorldSpaceTolerance = m_renderSettings->pickWorldSpaceTolerance(); |
| |
| EntityCasterGatherer gatherer(m_manager); |
| gatherer.apply(m_node); |
| const EntityCasterGatherer::EntityCasterList &entities = gatherer.m_result; |
| |
| PickingUtils::ViewportCameraAreaGatherer vcaGatherer; |
| const QVector<PickingUtils::ViewportCameraAreaDetails> vcaDetails = vcaGatherer.gather(m_frameGraphRoot); |
| |
| const float sceneRayLength = m_node->worldBoundingVolumeWithChildren()->radius() * 3.f; |
| |
| for (const EntityCasterGatherer::EntityCasterList::value_type &pair: entities) { |
| QVector<QRay3D> rays; |
| |
| switch (pair.second->type()) { |
| case QAbstractRayCasterPrivate::WorldSpaceRayCaster: |
| rays << QRay3D(Vector3D(pair.second->origin()), |
| Vector3D(pair.second->direction()), |
| pair.second->length() > 0.f ? pair.second->length() : sceneRayLength); |
| rays.back().transform(*pair.first->worldTransform()); |
| break; |
| case QAbstractRayCasterPrivate::ScreenScapeRayCaster: |
| for (const PickingUtils::ViewportCameraAreaDetails &vca : vcaDetails) { |
| auto ray = rayForViewportAndCamera(vca, nullptr, pair.second->position()); |
| if (ray.isValid()) |
| rays << ray; |
| } |
| break; |
| default: |
| Q_UNREACHABLE(); |
| } |
| |
| for (const QRay3D &ray: qAsConst(rays)) { |
| PickingUtils::HitList sphereHits; |
| PickingUtils::HierarchicalEntityPicker entityPicker(ray, false); |
| entityPicker.setFilterLayers(pair.second->layerIds(), pair.second->filterMode()); |
| if (entityPicker.collectHits(m_manager, m_node)) { |
| if (trianglePickingRequested) { |
| PickingUtils::TriangleCollisionGathererFunctor gathererFunctor; |
| gathererFunctor.m_frontFaceRequested = frontFaceRequested; |
| gathererFunctor.m_backFaceRequested = backFaceRequested; |
| gathererFunctor.m_manager = m_manager; |
| gathererFunctor.m_ray = ray; |
| gathererFunctor.m_objectPickersRequired = false; |
| sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); |
| } |
| if (edgePickingRequested) { |
| PickingUtils::LineCollisionGathererFunctor gathererFunctor; |
| gathererFunctor.m_manager = m_manager; |
| gathererFunctor.m_ray = ray; |
| gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; |
| gathererFunctor.m_objectPickersRequired = false; |
| sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); |
| PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); |
| } |
| if (pointPickingRequested) { |
| PickingUtils::PointCollisionGathererFunctor gathererFunctor; |
| gathererFunctor.m_manager = m_manager; |
| gathererFunctor.m_ray = ray; |
| gathererFunctor.m_pickWorldSpaceTolerance = pickWorldSpaceTolerance; |
| gathererFunctor.m_objectPickersRequired = false; |
| sphereHits << gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks); |
| PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); |
| } |
| if (!primitivePickingRequested) { |
| sphereHits << entityPicker.hits(); |
| PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits); |
| } |
| } |
| |
| dispatchHits(pair.second, sphereHits); |
| } |
| } |
| |
| return true; |
| } |
| |
| void RayCastingJob::dispatchHits(RayCaster *rayCaster, const PickingUtils::HitList &sphereHits) |
| { |
| QAbstractRayCaster::Hits hits; |
| for (const PickingUtils::HitList::value_type &sphereHit: sphereHits) { |
| Entity *entity = m_manager->renderNodesManager()->lookupResource(sphereHit.m_entityId); |
| Vector3D localIntersection = sphereHit.m_intersection; |
| if (entity && entity->worldTransform()) |
| localIntersection = entity->worldTransform()->inverted() * localIntersection; |
| |
| QRayCasterHit::HitType hitType = QRayCasterHit::EntityHit; |
| switch (sphereHit.m_type) { |
| case RayCasting::QCollisionQueryResult::Hit::Entity: |
| break; |
| case RayCasting::QCollisionQueryResult::Hit::Triangle: |
| hitType = QRayCasterHit::TriangleHit; |
| break; |
| case RayCasting::QCollisionQueryResult::Hit::Edge: |
| hitType = QRayCasterHit::LineHit; |
| break; |
| case RayCasting::QCollisionQueryResult::Hit::Point: |
| hitType = QRayCasterHit::PointHit; |
| break; |
| default: Q_UNREACHABLE(); |
| } |
| |
| hits << QRayCasterHit{ |
| hitType, |
| sphereHit.m_entityId, |
| sphereHit.m_distance, |
| convertToQVector3D(localIntersection), |
| convertToQVector3D(sphereHit.m_intersection), |
| sphereHit.m_primitiveIndex, |
| sphereHit.m_vertexIndex[0], |
| sphereHit.m_vertexIndex[1], |
| sphereHit.m_vertexIndex[2] |
| }; |
| } |
| |
| Q_D(RayCastingJob); |
| d->dispatches.push_back({rayCaster, hits}); |
| } |
| |
| QT_END_NAMESPACE |