Revision control
++ // Copyright (C) 2018 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 "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
++
++ namespace Qt3DRender {
++ namespace Render {
++
++ using namespace Qt3DRender::RayCasting;
++
++ namespace {
++
++ class EntityCasterGatherer : public EntityVisitor
++ {
++ public:
++ using EntityCasterList = QList<QPair<Entity *, RayCaster *>>;
++ EntityCasterList m_result;
++ RayCaster *m_trigger;
++
++ explicit EntityCasterGatherer(NodeManagers *manager, RayCaster *trigger = nullptr)
++ : EntityVisitor(manager), m_trigger(trigger) {
++ setPruneDisabled(true);
++ }
++
++ Operation visit(Entity *entity) override {
++ const std::vector<RayCaster *> &components = entity->renderComponents<RayCaster>();
++ for (const auto c: components) {
++ if ((m_trigger == nullptr && c->isEnabled()) || (m_trigger != nullptr && m_trigger == c))
++ m_result.push_back(qMakePair(entity, c));
++ }
++
++ return Continue;
++ }
++ };
++
++ } // anonymous
++
++ class 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;
++
++ QList<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: std::as_const(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;
++
++ EntityCasterGatherer gatherer(m_manager);
++ gatherer.apply(m_node);
++ const EntityCasterGatherer::EntityCasterList &entities = gatherer.m_result;
++ return pick(entities);
++ }
++
++ bool RayCastingJob::pick(const QList<QPair<Entity *, RayCaster *>> &entities)
++ {
++ const PickingUtils::PickConfiguration pickConfiguration(m_frameGraphRoot, m_renderSettings);
++ if (pickConfiguration.vcaDetails.empty())
++ return false;
++
++ const float sceneRayLength = m_node->worldBoundingVolumeWithChildren()->radius() * 3.f;
++
++ for (const EntityCasterGatherer::EntityCasterList::value_type &pair: entities) {
++ std::vector<QRay3D> rays;
++
++ switch (pair.second->type()) {
++ case QAbstractRayCasterPrivate::WorldSpaceRayCaster:
++ rays.emplace_back(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::ScreenSpaceRayCaster:
++ for (const PickingUtils::ViewportCameraAreaDetails &vca : pickConfiguration.vcaDetails) {
++ const auto ray = rayForViewportAndCamera(vca, nullptr, pair.second->position());
++ if (ray.isValid())
++ rays.push_back(ray);
++ }
++ break;
++ default:
++ Q_UNREACHABLE();
++ }
++
++ for (const QRay3D &ray: std::as_const(rays)) {
++ PickingUtils::HitList sphereHits;
++ PickingUtils::HierarchicalEntityPicker entityPicker(ray, false);
++ entityPicker.setLayerIds(pair.second->layerIds(), pair.second->filterMode());
++ if (entityPicker.collectHits(m_manager, m_node)) {
++ if (pickConfiguration.trianglePickingRequested) {
++ PickingUtils::TriangleCollisionGathererFunctor gathererFunctor;
++ gathererFunctor.m_frontFaceRequested = pickConfiguration.frontFaceRequested;
++ gathererFunctor.m_backFaceRequested = pickConfiguration.backFaceRequested;
++ gathererFunctor.m_manager = m_manager;
++ gathererFunctor.m_ray = ray;
++ gathererFunctor.m_objectPickersRequired = false;
++ PickingUtils::HitList hits = gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks);
++ Qt3DCore::moveAtEnd(sphereHits, std::move(hits));
++ }
++ if (pickConfiguration.edgePickingRequested) {
++ PickingUtils::LineCollisionGathererFunctor gathererFunctor;
++ gathererFunctor.m_manager = m_manager;
++ gathererFunctor.m_ray = ray;
++ gathererFunctor.m_pickWorldSpaceTolerance = pickConfiguration.pickWorldSpaceTolerance;
++ gathererFunctor.m_objectPickersRequired = false;
++ PickingUtils::HitList hits = gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks);
++ Qt3DCore::moveAtEnd(sphereHits, std::move(hits));
++ PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
++ }
++ if (pickConfiguration.pointPickingRequested) {
++ PickingUtils::PointCollisionGathererFunctor gathererFunctor;
++ gathererFunctor.m_manager = m_manager;
++ gathererFunctor.m_ray = ray;
++ gathererFunctor.m_pickWorldSpaceTolerance = pickConfiguration.pickWorldSpaceTolerance;
++ gathererFunctor.m_objectPickersRequired = false;
++ PickingUtils::HitList hits = gathererFunctor.computeHits(entityPicker.entities(), QPickingSettings::AllPicks);
++ Qt3DCore::moveAtEnd(sphereHits, std::move(hits));
++ PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
++ }
++ if (!pickConfiguration.primitivePickingRequested) {
++ PickingUtils::HitList hits = entityPicker.hits();
++ Qt3DCore::moveAtEnd(sphereHits, std::move(hits));
++ PickingUtils::AbstractCollisionGathererFunctor::sortHits(sphereHits);
++ }
++ }
++
++ dispatchHits(pair.second, sphereHits);
++ }
++ }
++
++ return true;
++ }
++
++ QAbstractRayCaster::Hits RayCastingJob::pick(Qt3DRender::QAbstractRayCaster *rayCaster)
++ {
++ const PickingUtils::PickConfiguration pickConfiguration(m_frameGraphRoot, m_renderSettings);
++ if (pickConfiguration.vcaDetails.empty())
++ return {};
++
++ auto backendRayCaster = m_manager->rayCasterManager()->lookupResource(rayCaster->id());
++ if (!backendRayCaster)
++ return {};
++
++ backendRayCaster->syncFromFrontEnd(rayCaster, false);
++
++ EntityCasterGatherer gatherer(m_manager, backendRayCaster);
++ gatherer.apply(m_node);
++ const EntityCasterGatherer::EntityCasterList &entities = gatherer.m_result;
++
++ if (!pick(entities))
++ return {};
++
++ Q_D(RayCastingJob);
++ QAbstractRayCaster::Hits res;
++ for (const auto &hit: d->dispatches) {
++ if (hit.first->peerId() == rayCaster->id()) {
++ res = hit.second;
++ break;
++ }
++ }
++ d->dispatches.clear();
++ return res;
++ }
++
++ 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().map(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});
++ }
++
++ } // namespace Render
++ } // namespace Qt3DRender
++
++ QT_END_NAMESPACE