X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=dali%2Finternal%2Fevent%2Fevents%2Fhit-test-algorithm-impl.cpp;h=c5d3753f96047b13d91203e8668eba8338abfd99;hb=d6c5b858d3327f46aee2a4a9c2afc23e12f826da;hp=49df17cfbd5b41b930c480ac90f65a886b898ad2;hpb=b45c4b2bd224350c488d6f103eb786308f3c235b;p=platform%2Fcore%2Fuifw%2Fdali-core.git diff --git a/dali/internal/event/events/hit-test-algorithm-impl.cpp b/dali/internal/event/events/hit-test-algorithm-impl.cpp index 49df17c..ec5cc7c 100644 --- a/dali/internal/event/events/hit-test-algorithm-impl.cpp +++ b/dali/internal/event/events/hit-test-algorithm-impl.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * Copyright (c) 2021 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,52 +19,41 @@ #include // INTERNAL INCLUDES -#include -#include -#include #include #include #include #include #include -#include -#include -#include #include -#include +#include #include #include +#include +#include +#include +#include namespace Dali { - namespace Internal { - namespace HitTestAlgorithm { - namespace { - struct HitActor { HitActor() - : actor( NULL ), - x( 0 ), - y( 0 ), - distance( std::numeric_limits::max() ), - overlay( false ) + : actor(nullptr), + distance(std::numeric_limits::max()), + depth(std::numeric_limits::min()) { - } - Actor *actor; ///< the actor hit. (if actor hit, then initialised) - float x; ///< x position of hit (only valid if actor valid) - float y; ///< y position of hit (only valid if actor valid) - float distance; ///< distance from ray origin to hit actor - bool overlay; ///< true if the hit actor is an overlay - + Actor* actor; ///< The actor hit (if actor is hit, then this is initialised). + Vector2 hitPosition; ///< Position of hit (only valid if actor valid). + float distance; ///< Distance from ray origin to hit actor. + int32_t depth; ///< Depth index of this actor. }; /** @@ -77,19 +66,27 @@ struct HitTestFunctionWrapper : public HitTestInterface * * @param[in] func HitTestFunction to call with an Actor handle. */ - HitTestFunctionWrapper( Dali::HitTestAlgorithm::HitTestFunction func ) - : mFunc( func ) + HitTestFunctionWrapper(Dali::HitTestAlgorithm::HitTestFunction func) + : mFunc(func) { } - virtual bool IsActorHittable( Actor* actor ) + bool IsActorHittable(Actor* actor) override { - return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::CHECK_ACTOR ); + return mFunc(Dali::Actor(actor), Dali::HitTestAlgorithm::CHECK_ACTOR); } - virtual bool DescendActorHierarchy( Actor* actor ) + bool DescendActorHierarchy(Actor* actor) override { - return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE ); + return mFunc(Dali::Actor(actor), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE); + } + + bool DoesLayerConsumeHit(Layer* layer) override + { + // Layer::IsTouchConsumed() focuses on touch only. Here we are a wrapper for the public-api + // where the caller may want to check for something completely different. + // TODO: Should provide a means to let caller decide. For now do not allow layers to consume + return false; } Dali::HitTestAlgorithm::HitTestFunction mFunc; @@ -101,151 +98,263 @@ struct HitTestFunctionWrapper : public HitTestInterface */ struct ActorTouchableCheck : public HitTestInterface { - virtual bool IsActorHittable( Actor* actor ) + bool IsActorHittable(Actor* actor) override { - return actor->GetTouchRequired() && // Does the Application or derived actor type require a touch event? - actor->IsHittable(); // Is actor sensitive, visible and on the scene? + return (actor->GetTouchRequired() || actor->GetInterceptTouchRequired() || actor->IsTouchFocusable()) && // Does the Application or derived actor type require a touch event or a intercept touch event? or focusable by touch? + actor->IsHittable(); // Is actor sensitive, visible and on the scene? } - virtual bool DescendActorHierarchy( Actor* actor ) + bool DescendActorHierarchy(Actor* actor) override { return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible. actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either. } + + bool DoesLayerConsumeHit(Layer* layer) override + { + return layer->IsTouchConsumed(); + } }; /** + * Check to see if the actor we're about to hit test is exclusively owned by another rendertask? + */ +bool IsActorExclusiveToAnotherRenderTask(const Actor& actor, + const RenderTask& renderTask, + const RenderTaskList::ExclusivesContainer& exclusives) + +{ + if(exclusives.size()) + { + for(const auto& exclusive : exclusives) + { + if((exclusive.renderTaskPtr != &renderTask) && (exclusive.actor.GetActor() == &actor)) + { + return true; + } + } + } + return false; +} + +/** * Recursively hit test all the actors, without crossing into other layers. * This algorithm performs a Depth-First-Search (DFS) on all Actors within Layer. * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin * of touch vector). The closest Hit-Tested Actor is that which is returned. * Exceptions to this rule are: - * - If the Actor is an overlay then it is considered closer than all previous - * overlays encountered in the hit test traversal. * - When comparing against renderable parents, if Actor is the same distance * or closer than it's renderable parent, then it takes priority. */ -HitActor HitTestWithinLayer( Actor& actor, - const Vector4& rayOrigin, - const Vector4& rayDir, - bool worldOverlay, - float& nearClippingPlane, - float& farClippingPlane, - HitTestInterface& hitCheck, - bool& stencilOnLayer, - bool& stencilHit, - bool parentIsStencil ) +HitActor HitTestWithinLayer(Actor& actor, + const RenderTask& renderTask, + const RenderTaskList::ExclusivesContainer& exclusives, + const Vector4& rayOrigin, + const Vector4& rayDir, + float& nearClippingPlane, + float& farClippingPlane, + HitTestInterface& hitCheck, + bool& overlayHit, + bool layerIs3d, + uint32_t clippingDepth, + uint32_t clippingBitPlaneMask, + const RayTest& rayTest) { - worldOverlay |= actor.IsOverlay(); - HitActor hit; - // Children should inherit the stencil draw mode - bool isStencil = parentIsStencil; + if(IsActorExclusiveToAnotherRenderTask(actor, renderTask, exclusives)) + { + return hit; + } - if ( actor.GetDrawMode() == DrawMode::STENCIL && actor.IsVisible() ) + // For clipping, regardless of whether we have hit this actor or not, + // we increase the clipping depth if we have hit a clipping actor. + // This is used later to ensure all nested clipped children have hit + // all clipping actors also for them to be counted as hit. + uint32_t newClippingDepth = clippingDepth; + bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED; + if(clippingActor) { - isStencil = true; - stencilOnLayer = true; + ++newClippingDepth; } - // If we are a stencil or hittable... - if ( isStencil || hitCheck.IsActorHittable( &actor ) ) + // If we are a clipping actor or hittable... + if(clippingActor || hitCheck.IsActorHittable(&actor)) { - Vector3 size( actor.GetCurrentSize() ); + Vector3 size(actor.GetCurrentSize()); - if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size. - actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor. + // Ensure the actor has a valid size. + // If so, perform a quick ray sphere test to see if our ray is close to the actor. + if(size.x > 0.0f && size.y > 0.0f && rayTest.SphereTest(actor, rayOrigin, rayDir)) { - Vector4 hitPointLocal; - float distance; + Vector2 hitPointLocal; + float distance; // Finally, perform a more accurate ray test to see if our ray actually hits the actor. - if( actor.RayActorTest( rayOrigin, rayDir, hitPointLocal, distance ) ) + if(rayTest.ActorTest(actor, rayOrigin, rayDir, hitPointLocal, distance)) { - if( distance >= nearClippingPlane && distance <= farClippingPlane ) + // Calculate z coordinate value in Camera Space. + const Matrix& viewMatrix = renderTask.GetCameraActor()->GetViewMatrix(); + const Vector4& hitDir = Vector4(rayDir.x * distance, rayDir.y * distance, rayDir.z * distance, 0.0f); + const float cameraDepthDistance = (viewMatrix * hitDir).z; + + // Check if cameraDepthDistance is between clipping plane + if(cameraDepthDistance >= nearClippingPlane && cameraDepthDistance <= farClippingPlane) { - // If the hit has happened on a stencil then register, but don't record as hit result - if ( isStencil ) + // If the hit has happened on a clipping actor, then add this clipping depth to the mask of hit clipping depths. + // This mask shows all the actors that have been hit at different clipping depths. + if(clippingActor) { - stencilHit = true; + clippingBitPlaneMask |= 1u << clippingDepth; + } + + if(overlayHit && !actor.IsOverlay()) + { + // If we have already hit an overlay and current actor is not an overlay ignore current actor. } else { - hit.actor = &actor; - hit.x = hitPointLocal.x; - hit.y = hitPointLocal.y; - hit.distance = distance; - hit.overlay = worldOverlay; + if(actor.IsOverlay()) + { + overlayHit = true; + } + + // At this point we have hit an actor. + // Now perform checks for clipping. + // Assume we have hit the actor first as if it is not clipped this would be the case. + bool haveHitActor = true; + + // Check if we are performing clipping. IE. if any actors so far have clipping enabled - not necessarily this one. + // We can do this by checking the clipping depth has a value 1 or above. + if(newClippingDepth >= 1u) + { + // Now for us to count this actor as hit, we must have also hit + // all CLIPPING actors up to this point in the hierarchy as well. + // This information is stored in the clippingBitPlaneMask we updated above. + // Here we calculate a comparison mask by setting all the bits up to the current depth value. + // EG. a depth of 4 (10000 binary) = a mask of 1111 binary. + // This allows us a fast way of comparing all bits are set up to this depth. + // Note: If the current Actor has clipping, that is included in the depth mask too. + uint32_t clippingDepthMask = (1u << newClippingDepth) - 1u; + + // The two masks must be equal to be a hit, as we are already assuming a hit + // (for non-clipping mode) then they must be not-equal to disqualify the hit. + if(clippingBitPlaneMask != clippingDepthMask) + { + haveHitActor = false; + } + } + + if(haveHitActor) + { + hit.actor = &actor; + hit.hitPosition = hitPointLocal; + hit.distance = distance; + hit.depth = actor.GetSortingDepth(); + + if(actor.GetRendererCount() > 0) + { + //Get renderer with maximum depth + int rendererMaxDepth(actor.GetRendererAt(0).Get()->GetDepthIndex()); + for(uint32_t i(1); i < actor.GetRendererCount(); ++i) + { + int depth = actor.GetRendererAt(i).Get()->GetDepthIndex(); + if(depth > rendererMaxDepth) + { + rendererMaxDepth = depth; + } + } + hit.depth += rendererMaxDepth; + } + } } } } } } - // If we are a stencil (or a child of a stencil) and we have already ascertained that the stencil has been hit then there is no need to hit-test the children of this stencil-actor - if ( isStencil && stencilHit ) - { - return hit; - } - // Find a child hit, until we run out of actors in the current layer. HitActor childHit; - if( actor.GetChildCount() > 0 ) + if(actor.GetChildCount() > 0) { - childHit.distance = std::numeric_limits::max(); - Dali::ActorContainer& children = actor.GetChildrenInternal(); + childHit.distance = std::numeric_limits::max(); + childHit.depth = std::numeric_limits::min(); + ActorContainer& children = actor.GetChildrenInternal(); // Hit test ALL children and calculate their distance. bool parentIsRenderable = actor.IsRenderable(); - for (Dali::ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter) + for(ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter) { // Descend tree only if... - if ( !iter->IsLayer() && // Child is NOT a layer, hit testing current layer only or Child is not a layer and we've inherited the stencil draw mode - ( isStencil || hitCheck.DescendActorHierarchy( &GetImplementation( *iter ) ) ) ) // We are a stencil OR we can descend into child hierarchy + if(!(*iter)->IsLayer() && // Child is NOT a layer, hit testing current layer only + (hitCheck.DescendActorHierarchy((*iter).Get()))) // We can descend into child hierarchy { - HitActor currentHit( HitTestWithinLayer( GetImplementation(*iter), rayOrigin, rayDir, worldOverlay, nearClippingPlane, farClippingPlane, hitCheck, stencilOnLayer, stencilHit, isStencil ) ); + HitActor currentHit(HitTestWithinLayer((*iter->Get()), + renderTask, + exclusives, + rayOrigin, + rayDir, + nearClippingPlane, + farClippingPlane, + hitCheck, + overlayHit, + layerIs3d, + newClippingDepth, + clippingBitPlaneMask, + rayTest)); + + // Make sure the set hit actor is actually hittable. This is usually required when we have some + // clipping as we need to hit-test all actors as we descend the tree regardless of whether they + // are hittable or not. + if(currentHit.actor && !hitCheck.IsActorHittable(currentHit.actor)) + { + continue; + } - // If Current child is an overlay, then it takes priority. - // If it is not an overlay, and the previously hit sibling is also not an overlay, then closest takes priority. - // (last overlay sibling has priority as is rendered on top) - if ( currentHit.distance >= 0.f && (currentHit.overlay || (!childHit.overlay && currentHit.distance < childHit.distance) ) ) + bool updateChildHit = false; + if(currentHit.distance >= 0.0f) { - if ( !parentIsRenderable ) + if(layerIs3d) { - // If our parent is not renderable, then child should be hit regardless of distance. - childHit = currentHit; + updateChildHit = ((currentHit.depth > childHit.depth) || + ((currentHit.depth == childHit.depth) && (currentHit.distance < childHit.distance))); } - else if ( currentHit.overlay || (!hit.overlay && currentHit.distance <= hit.distance) ) + else + { + updateChildHit = currentHit.depth >= childHit.depth; + } + } + + if(updateChildHit) + { + if(!parentIsRenderable || currentHit.depth > hit.depth || + (layerIs3d && (currentHit.depth == hit.depth && currentHit.distance < hit.distance))) { - // If our parent is renderable, then child should only be hit if it is an overlay, or if it is closer than a non-overlay. - // (child overlay has priority as is rendered on top of it's parent) childHit = currentHit; } } } } } - return ( childHit.actor ) ? childHit : hit; + + return (childHit.actor) ? childHit : hit; } /** * Return true if actor is sourceActor or a descendent of sourceActor */ -bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor ) +bool IsWithinSourceActors(const Actor& sourceActor, const Actor& actor) { - if ( &sourceActor == &actor ) + if(&sourceActor == &actor) { return true; } - else + + Actor* parent = actor.GetParent(); + if(parent) { - Actor* parent = actor.GetParent(); - if ( parent ) - { - return IsWithinSourceActors( sourceActor, *parent ); - } + return IsWithinSourceActors(sourceActor, *parent); } // Not within source actors @@ -255,18 +364,18 @@ bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor ) /** * Returns true if the layer and all of the layer's parents are visible and sensitive. */ -inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck ) +inline bool IsActuallyHittable(Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck) { - bool hittable( true ); + bool hittable(true); if(layer.IsClipping()) { ClippingBox box = layer.GetClippingBox(); - if( screenCoordinates.x < box.x || - screenCoordinates.x > box.x + box.width || - screenCoordinates.y < stageSize.y - (box.y + box.height) || - screenCoordinates.y > stageSize.y - box.y) + if(screenCoordinates.x < static_cast(box.x) || + screenCoordinates.x > static_cast(box.x + box.width) || + screenCoordinates.y < stageSize.y - static_cast(box.y + box.height) || + screenCoordinates.y > stageSize.y - static_cast(box.y)) { // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region. hittable = false; @@ -275,12 +384,12 @@ inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, if(hittable) { - Actor* actor( &layer ); + Actor* actor(&layer); // Ensure that we can descend into the layer's (or any of its parent's) hierarchy. - while ( actor && hittable ) + while(actor && hittable) { - if ( ! hitCheck.DescendActorHierarchy( actor ) ) + if(!hitCheck.DescendActorHierarchy(actor)) { hittable = false; break; @@ -295,30 +404,33 @@ inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, /** * Gets the near and far clipping planes of the camera from which the scene is viewed in the render task. */ -void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane ) +void GetCameraClippingPlane(RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane) { CameraActor* cameraActor = renderTask.GetCameraActor(); - nearClippingPlane = cameraActor->GetNearClippingPlane(); - farClippingPlane = cameraActor->GetFarClippingPlane(); + nearClippingPlane = cameraActor->GetNearClippingPlane(); + farClippingPlane = cameraActor->GetFarClippingPlane(); } /** * Hit test a RenderTask */ -bool HitTestRenderTask( LayerList& layers, - RenderTask& renderTask, - Vector2 screenCoordinates, - Results& results, - HitTestInterface& hitCheck ) +bool HitTestRenderTask(const RenderTaskList::ExclusivesContainer& exclusives, + const Vector2& sceneSize, + LayerList& layers, + RenderTask& renderTask, + Vector2 screenCoordinates, + Results& results, + HitTestInterface& hitCheck, + const RayTest& rayTest) { - if ( renderTask.IsHittable( screenCoordinates ) ) + if(renderTask.IsHittable(screenCoordinates)) { Viewport viewport; - renderTask.GetViewport( viewport ); - if( screenCoordinates.x < viewport.x || - screenCoordinates.x > viewport.x + viewport.width || - screenCoordinates.y < viewport.y || - screenCoordinates.y > viewport.y + viewport.height ) + renderTask.GetViewport(viewport); + if(screenCoordinates.x < static_cast(viewport.x) || + screenCoordinates.x > static_cast(viewport.x + viewport.width) || + screenCoordinates.y < static_cast(viewport.y) || + screenCoordinates.y > static_cast(viewport.y + viewport.height)) { // The screen coordinate is outside the viewport of render task. The viewport clips all layers. return false; @@ -328,68 +440,96 @@ bool HitTestRenderTask( LayerList& layers, GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane); // Determine the layer depth of the source actor - Actor* sourceActor( renderTask.GetSourceActor() ); - if ( sourceActor ) + Actor* sourceActor(renderTask.GetSourceActor()); + if(sourceActor) { - Dali::Layer layer( sourceActor->GetLayer() ); - if ( layer ) + Dali::Layer layer(sourceActor->GetLayer()); + if(layer) { - const unsigned int sourceActorDepth( layer.GetDepth() ); - - CameraActor* cameraActor = renderTask.GetCameraActor(); - bool pickingPossible = cameraActor->BuildPickingRay( - screenCoordinates, - viewport, - results.rayOrigin, - results.rayDirection ); - if( !pickingPossible ) + const uint32_t sourceActorDepth(layer.GetProperty(Dali::Layer::Property::DEPTH)); + + CameraActor* cameraActor = renderTask.GetCameraActor(); + bool pickingPossible = cameraActor->BuildPickingRay( + screenCoordinates, + viewport, + results.rayOrigin, + results.rayDirection); + if(!pickingPossible) { return false; } // Hit test starting with the top layer, working towards the bottom layer. HitActor hit; - bool stencilOnLayer = false; - bool stencilHit = false; - const Vector2& stageSize = Stage::GetCurrent()->GetSize(); + bool overlayHit = false; + bool layerConsumesHit = false; - for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i) + for(int32_t i = layers.GetLayerCount() - 1; i >= 0 && !(hit.actor); --i) { - Layer* layer( layers.GetLayer(i) ); - - HitActor previousHit = hit; - stencilOnLayer = false; - stencilHit = false; + Layer* layer(layers.GetLayer(i)); + overlayHit = false; // Ensure layer is touchable (also checks whether ancestors are also touchable) - if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, hitCheck ) ) + if(IsActuallyHittable(*layer, screenCoordinates, sceneSize, hitCheck)) { // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy - if ( sourceActorDepth == static_cast(i) ) + if(sourceActorDepth == static_cast(i)) { // Recursively hit test the source actor & children, without crossing into other layers. - hit = HitTestWithinLayer( *sourceActor, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, hitCheck, stencilOnLayer, stencilHit, false ); + hit = HitTestWithinLayer(*sourceActor, + renderTask, + exclusives, + results.rayOrigin, + results.rayDirection, + nearClippingPlane, + farClippingPlane, + hitCheck, + overlayHit, + layer->GetBehavior() == Dali::Layer::LAYER_3D, + 0u, + 0u, + rayTest); } - else if ( IsWithinSourceActors( *sourceActor, *layer ) ) + else if(IsWithinSourceActors(*sourceActor, *layer)) { // Recursively hit test all the actors, without crossing into other layers. - hit = HitTestWithinLayer( *layer, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, hitCheck, stencilOnLayer, stencilHit, false ); + hit = HitTestWithinLayer(*layer, + renderTask, + exclusives, + results.rayOrigin, + results.rayDirection, + nearClippingPlane, + farClippingPlane, + hitCheck, + overlayHit, + layer->GetBehavior() == Dali::Layer::LAYER_3D, + 0u, + 0u, + rayTest); } - // If a stencil on this layer hasn't been hit, then discard hit results for this layer - if ( stencilOnLayer && !stencilHit ) + + // If this layer is set to consume the hit, then do not check any layers behind it + if(hitCheck.DoesLayerConsumeHit(layer)) { - hit = previousHit; + layerConsumesHit = true; + break; } } } - if ( hit.actor ) + + if(hit.actor) { - results.renderTask = Dali::RenderTask(&renderTask); - results.actor = Dali::Actor(hit.actor); - results.actorCoordinates.x = hit.x; - results.actorCoordinates.y = hit.y; + results.renderTask = RenderTaskPtr(&renderTask); + results.actor = Dali::Actor(hit.actor); + results.actorCoordinates = hit.hitPosition; + return true; // Success } + + if(layerConsumesHit) + { + return true; // Also success if layer is consuming the hit + } } } } @@ -397,126 +537,116 @@ bool HitTestRenderTask( LayerList& layers, } /** - * Iterate through RenderTaskList and perform hit test. + * Iterate through the RenderTaskList and perform hit testing. + * + * @param[in] sceneSize The scene size the tests will be performed in + * @param[in] layers The list of layers to test + * @param[in] taskList The list of render tasks + * @param[out] results Ray information calculated by the camera + * @param[in] hitCheck The hit testing interface object to use + * @param[in] onScreen True to test on-screen, false to test off-screen + * @return True if we have a hit, false otherwise */ - -void HitTestForEachRenderTask( LayerList& layers, - RenderTaskList& taskList, - const Vector2& screenCoordinates, - Results& results, - HitTestInterface& hitCheck ) +bool HitTestRenderTaskList(const Vector2& sceneSize, + LayerList& layers, + RenderTaskList& taskList, + const Vector2& screenCoordinates, + Results& results, + HitTestInterface& hitCheck, + bool onScreen) { - RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks(); - RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend(); + RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks(); + RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend(); + const auto& exclusives = taskList.GetExclusivesList(); + RayTest rayTest; - // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first). - - // on screen - for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter ) + for(RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter) { - RenderTask& renderTask = GetImplementation( *iter ); - Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer(); - - // Note that if frameBufferImage is NULL we are using the default (on screen) render target - if(frameBufferImage) + RenderTask& renderTask = *iter->Get(); + const bool isOffscreenRenderTask = renderTask.GetFrameBuffer(); + if((onScreen && isOffscreenRenderTask) || (!onScreen && !isOffscreenRenderTask)) { - ResourceId id = GetImplementation(frameBufferImage).GetResourceId(); - - // on screen only - if(0 != id) - { - // Skip to next task - continue; - } + // Skip to next task + continue; } - if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, hitCheck ) ) + if(HitTestRenderTask(exclusives, sceneSize, layers, renderTask, screenCoordinates, results, hitCheck, rayTest)) { - // Exit when an actor is hit - return; // don't bother checking off screen tasks + // Return true when an actor is hit (or layer in our render-task consumes the hit) + return true; // don't bother checking off screen tasks } } - // off screen - for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter ) - { - RenderTask& renderTask = GetImplementation( *iter ); - Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer(); - - // Note that if frameBufferImage is NULL we are using the default (on screen) render target - if(frameBufferImage) - { - ResourceId id = GetImplementation(frameBufferImage).GetResourceId(); + return false; +} - // off screen only - if(0 == id) - { - // Skip to next task - continue; - } +/** + * Iterate through the RenderTaskList and perform hit testing for both on-screen and off-screen. + * + * @param[in] sceneSize The scene size the tests will be performed in + * @param[in] layers The list of layers to test + * @param[in] taskList The list of render tasks + * @param[out] results Ray information calculated by the camera + * @param[in] hitCheck The hit testing interface object to use + * @param[in] onScreen True to test on-screen, false to test off-screen + * @return True if we have a hit, false otherwise + */ +bool HitTestForEachRenderTask(const Vector2& sceneSize, + LayerList& layers, + RenderTaskList& taskList, + const Vector2& screenCoordinates, + Results& results, + HitTestInterface& hitCheck) +{ + bool result = false; - if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, hitCheck ) ) - { - // Exit when an actor is hit - break; - } - } + // Check on-screen tasks before off-screen ones. + // Hit test order should be reverse of draw order (see ProcessRenderTasks() where off-screen tasks are drawn first). + if(HitTestRenderTaskList(sceneSize, layers, taskList, screenCoordinates, results, hitCheck, true) || + HitTestRenderTaskList(sceneSize, layers, taskList, screenCoordinates, results, hitCheck, false)) + { + // Found hit. + result = true; } + + return result; } } // unnamed namespace -void HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func ) -{ - // Hit-test the regular on-stage actors - RenderTaskList& taskList = stage.GetRenderTaskList(); - LayerList& layerList = stage.GetLayerList(); - - Results hitTestResults; - HitTestFunctionWrapper hitTestFunctionWrapper( func ); - HitTestForEachRenderTask( layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ); - - results.actor = hitTestResults.actor; - results.actorCoordinates = hitTestResults.actorCoordinates; -} +HitTestInterface::~HitTestInterface() = default; -void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface ) +bool HitTest(const Vector2& sceneSize, RenderTaskList& taskList, LayerList& layerList, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func) { - // Hit-test the system-overlay actors first - SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal(); - - if ( systemOverlay ) + bool wasHit(false); + // Hit-test the regular on-scene actors + Results hitTestResults; + HitTestFunctionWrapper hitTestFunctionWrapper(func); + if(HitTestForEachRenderTask(sceneSize, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper)) { - RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks(); - LayerList& overlayLayerList = systemOverlay->GetLayerList(); - - HitTestForEachRenderTask( overlayLayerList, overlayTaskList, screenCoordinates, results, hitTestInterface ); + results.actor = hitTestResults.actor; + results.actorCoordinates = hitTestResults.actorCoordinates; + wasHit = true; } + return wasHit; +} - // Hit-test the regular on-stage actors - if ( !results.actor ) - { - RenderTaskList& taskList = stage.GetRenderTaskList(); - LayerList& layerList = stage.GetLayerList(); +bool HitTest(const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface) +{ + bool wasHit(false); - HitTestForEachRenderTask( layerList, taskList, screenCoordinates, results, hitTestInterface ); + // Hit-test the regular on-scene actors + if(!wasHit) + { + wasHit = HitTestForEachRenderTask(sceneSize, layerList, renderTaskList, screenCoordinates, results, hitTestInterface); } + return wasHit; } -void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results ) +bool HitTest(const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results) { ActorTouchableCheck actorTouchableCheck; - HitTest( stage, screenCoordinates, results, actorTouchableCheck ); -} - -void HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates, - Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func ) -{ - Results hitTestResults; - HitTestFunctionWrapper hitTestFunctionWrapper( func ); - HitTestRenderTask( stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ); - results.actor = hitTestResults.actor; - results.actorCoordinates = hitTestResults.actorCoordinates; + return HitTest(sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck); } } // namespace HitTestAlgorithm