(HitTest) Check clipped actor if it is really touchable before using as hit-actor
[platform/core/uifw/dali-core.git] / dali / internal / event / events / hit-test-algorithm-impl.cpp
index 5026b95..d47f35d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2020 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.
 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
 
 // INTERNAL INCLUDES
-#include <dali/integration-api/system-overlay.h>
 #include <dali/public-api/actors/layer.h>
 #include <dali/public-api/math/vector2.h>
 #include <dali/public-api/math/vector4.h>
 #include <dali/integration-api/debug.h>
 #include <dali/internal/event/actors/actor-impl.h>
 #include <dali/internal/event/actors/camera-actor-impl.h>
-#include <dali/internal/event/actors/image-actor-impl.h>
 #include <dali/internal/event/actors/layer-impl.h>
 #include <dali/internal/event/actors/layer-list.h>
-#include <dali/internal/event/common/system-overlay-impl.h>
-#include <dali/internal/event/common/stage-impl.h>
 #include <dali/internal/event/common/projection.h>
-#include <dali/internal/event/images/frame-buffer-image-impl.h>
+#include <dali/internal/event/events/ray-test.h>
 #include <dali/internal/event/render-tasks/render-task-impl.h>
 #include <dali/internal/event/render-tasks/render-task-list-impl.h>
 
@@ -51,16 +47,16 @@ namespace
 struct HitActor
 {
   HitActor()
-  : actor( NULL ),
+  : actor( nullptr ),
     distance( std::numeric_limits<float>::max() ),
     depth( std::numeric_limits<int>::min() )
   {
   }
 
-  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.
-  int depth;                            ///< Depth index of this actor.
+  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.
 };
 
 /**
@@ -78,17 +74,17 @@ struct HitTestFunctionWrapper : public HitTestInterface
   {
   }
 
-  virtual bool IsActorHittable( Actor* actor )
+  bool IsActorHittable( Actor* actor ) override
   {
     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 );
   }
 
-  virtual bool DoesLayerConsumeHit( Layer* layer )
+  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.
@@ -105,19 +101,19 @@ 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?
   }
 
-  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.
   }
 
-  virtual bool DoesLayerConsumeHit( Layer* layer )
+  bool DoesLayerConsumeHit( Layer* layer ) override
   {
     return layer->IsTouchConsumed();
   }
@@ -128,14 +124,14 @@ struct ActorTouchableCheck : public HitTestInterface
  */
 bool IsActorExclusiveToAnotherRenderTask( const Actor& actor,
                                           const RenderTask& renderTask,
-                                          const Vector< RenderTaskList::Exclusive >& exclusives )
+                                          const RenderTaskList::ExclusivesContainer& exclusives )
 
 {
-  if( exclusives.Size() )
+  if( exclusives.size() )
   {
-    for( Vector< RenderTaskList::Exclusive >::Iterator exclusiveIt = exclusives.Begin(); exclusives.End() != exclusiveIt; ++exclusiveIt )
+    for( const auto& exclusive : exclusives )
     {
-      if( ( exclusiveIt->renderTaskPtr != &renderTask ) && ( exclusiveIt->actorPtr == &actor ) )
+      if( ( exclusive.renderTaskPtr != &renderTask ) && ( exclusive.actor.GetActor() == &actor ) )
       {
         return true;
       }
@@ -155,7 +151,7 @@ bool IsActorExclusiveToAnotherRenderTask( const Actor& actor,
  */
 HitActor HitTestWithinLayer( Actor& actor,
                              const RenderTask& renderTask,
-                             const Vector< RenderTaskList::Exclusive >& exclusives,
+                             const RenderTaskList::ExclusivesContainer& exclusives,
                              const Vector4& rayOrigin,
                              const Vector4& rayDir,
                              float& nearClippingPlane,
@@ -163,8 +159,9 @@ HitActor HitTestWithinLayer( Actor& actor,
                              HitTestInterface& hitCheck,
                              bool& overlayHit,
                              bool layerIs3d,
-                             unsigned int clippingDepth,
-                             unsigned int clippingBitPlaneMask )
+                             uint32_t clippingDepth,
+                             uint32_t clippingBitPlaneMask,
+                             const RayTest& rayTest )
 {
   HitActor hit;
 
@@ -177,7 +174,7 @@ HitActor HitTestWithinLayer( Actor& actor,
   // 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.
-  unsigned int newClippingDepth = clippingDepth;
+  uint32_t newClippingDepth = clippingDepth;
   bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED;
   if( clippingActor )
   {
@@ -191,13 +188,13 @@ HitActor HitTestWithinLayer( Actor& 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 && actor.RaySphereTest( rayOrigin, rayDir ) )
+    if( size.x > 0.0f && size.y > 0.0f && rayTest.SphereTest( actor, rayOrigin, rayDir ) )
     {
       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 )
         {
@@ -234,7 +231,8 @@ HitActor HitTestWithinLayer( Actor& actor,
               // 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.
-              unsigned int clippingDepthMask = ( 1u << clippingDepth ) - 1u;
+              // 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.
@@ -249,13 +247,13 @@ HitActor HitTestWithinLayer( Actor& actor,
               hit.actor = &actor;
               hit.hitPosition = hitPointLocal;
               hit.distance = distance;
-              hit.depth = actor.GetHierarchyDepth() * Dali::Layer::TREE_DEPTH_MULTIPLIER;
+              hit.depth = actor.GetSortingDepth() ;
 
               if( actor.GetRendererCount() > 0 )
               {
                 //Get renderer with maximum depth
                 int rendererMaxDepth(actor.GetRendererAt( 0 ).Get()->GetDepthIndex());
-                for( unsigned int i(1); i < actor.GetRendererCount(); ++i )
+                for( uint32_t i(1); i < actor.GetRendererCount(); ++i )
                 {
                   int depth = actor.GetRendererAt( i ).Get()->GetDepthIndex();
                   if( depth > rendererMaxDepth )
@@ -277,7 +275,7 @@ HitActor HitTestWithinLayer( Actor& actor,
   if( actor.GetChildCount() > 0 )
   {
     childHit.distance = std::numeric_limits<float>::max();
-    childHit.depth = std::numeric_limits<int>::min();
+    childHit.depth = std::numeric_limits<int32_t>::min();
     ActorContainer& children = actor.GetChildrenInternal();
 
     // Hit test ALL children and calculate their distance.
@@ -300,7 +298,16 @@ HitActor HitTestWithinLayer( Actor& actor,
                                                  overlayHit,
                                                  layerIs3d,
                                                  newClippingDepth,
-                                                 clippingBitPlaneMask ) );
+                                                 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;
+        }
 
         bool updateChildHit = false;
         if( currentHit.distance >= 0.0f )
@@ -362,10 +369,10 @@ inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates,
   {
     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<float>( box.x )||
+        screenCoordinates.x > static_cast<float>( box.x + box.width )||
+        screenCoordinates.y < stageSize.y - static_cast<float>( box.y + box.height ) ||
+        screenCoordinates.y > stageSize.y - static_cast<float>( box.y ) )
     {
       // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
       hittable = false;
@@ -404,22 +411,23 @@ void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, f
 /**
  * Hit test a RenderTask
  */
-bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
-                        Stage& stage,
+bool HitTestRenderTask( const RenderTaskList::ExclusivesContainer& exclusives,
+                        const Vector2& sceneSize,
                         LayerList& layers,
                         RenderTask& renderTask,
                         Vector2 screenCoordinates,
                         Results& results,
-                        HitTestInterface& hitCheck )
+                        HitTestInterface& hitCheck,
+                        const RayTest& rayTest )
 {
   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 )
+    if( screenCoordinates.x < static_cast<float>( viewport.x ) ||
+        screenCoordinates.x > static_cast<float>( viewport.x + viewport.width ) ||
+        screenCoordinates.y < static_cast<float>( viewport.y ) ||
+        screenCoordinates.y > static_cast<float>( viewport.y + viewport.height ) )
     {
       // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
       return false;
@@ -435,7 +443,7 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
       Dali::Layer layer( sourceActor->GetLayer() );
       if( layer )
       {
-        const unsigned int sourceActorDepth( layer.GetDepth() );
+        const uint32_t sourceActorDepth( layer.GetProperty< bool >( Dali::Layer::Property::DEPTH ) );
 
         CameraActor* cameraActor = renderTask.GetCameraActor();
         bool pickingPossible = cameraActor->BuildPickingRay(
@@ -452,18 +460,17 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
         HitActor hit;
         bool overlayHit = false;
         bool layerConsumesHit = false;
-        const Vector2& stageSize = stage.GetSize();
 
-        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 ) );
           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<unsigned int>( i ) )
+            if( sourceActorDepth == static_cast<uint32_t>( i ) )
             {
               // Recursively hit test the source actor & children, without crossing into other layers.
               hit = HitTestWithinLayer( *sourceActor,
@@ -477,7 +484,8 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
                                         overlayHit,
                                         layer->GetBehavior() == Dali::Layer::LAYER_3D,
                                         0u,
-                                        0u );
+                                        0u,
+                                        rayTest );
             }
             else if( IsWithinSourceActors( *sourceActor, *layer ) )
             {
@@ -493,7 +501,8 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
                                         overlayHit,
                                         layer->GetBehavior() == Dali::Layer::LAYER_3D,
                                         0u,
-                                        0u );
+                                        0u,
+                                        rayTest );
             }
 
             // If this layer is set to consume the hit, then do not check any layers behind it
@@ -507,7 +516,7 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
 
         if( hit.actor )
         {
-          results.renderTask = Dali::RenderTask( &renderTask );
+          results.renderTask = RenderTaskPtr( &renderTask );
           results.actor = Dali::Actor( hit.actor );
           results.actorCoordinates = hit.hitPosition;
 
@@ -527,7 +536,7 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
 /**
  * Iterate through the RenderTaskList and perform hit testing.
  *
- * @param[in] stage The stage the tests will be performed in
+ * @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
@@ -535,7 +544,7 @@ bool HitTestRenderTask( const Vector< RenderTaskList::Exclusive >& exclusives,
  * @param[in] onScreen True to test on-screen, false to test off-screen
  * @return True if we have a hit, false otherwise
  */
-bool HitTestRenderTaskList( Stage& stage,
+bool HitTestRenderTaskList( const Vector2& sceneSize,
                             LayerList& layers,
                             RenderTaskList& taskList,
                             const Vector2& screenCoordinates,
@@ -545,27 +554,20 @@ bool HitTestRenderTaskList( Stage& stage,
 {
   RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
   RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
-  const Vector< RenderTaskList::Exclusive >& exclusives = taskList.GetExclusivesList();
+  const auto& exclusives = taskList.GetExclusivesList();
+  RayTest rayTest;
 
   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();
-
-      // Change comparison depending on if on-screen or off-screen.
-      if( onScreen ? ( 0 != id ) : ( 0 == id ) )
-      {
-        // Skip to next task
-        continue;
-      }
+      // Skip to next task
+      continue;
     }
 
-    if( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
+    if( HitTestRenderTask( exclusives, sceneSize, layers, renderTask, screenCoordinates, results, hitCheck, rayTest ) )
     {
       // 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
@@ -578,7 +580,7 @@ bool HitTestRenderTaskList( Stage& stage,
 /**
  * Iterate through the RenderTaskList and perform hit testing for both on-screen and off-screen.
  *
- * @param[in] stage The stage the tests will be performed in
+ * @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
@@ -586,7 +588,7 @@ bool HitTestRenderTaskList( Stage& stage,
  * @param[in] onScreen True to test on-screen, false to test off-screen
  * @return True if we have a hit, false otherwise
  */
-bool HitTestForEachRenderTask( Stage& stage,
+bool HitTestForEachRenderTask( const Vector2& sceneSize,
                                LayerList& layers,
                                RenderTaskList& taskList,
                                const Vector2& screenCoordinates,
@@ -597,8 +599,8 @@ bool HitTestForEachRenderTask( Stage& stage,
 
   // 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( stage, layers, taskList, screenCoordinates, results, hitCheck, true  ) ||
-      HitTestRenderTaskList( stage, layers, taskList, screenCoordinates, results, hitCheck, false ) )
+  if( HitTestRenderTaskList( sceneSize, layers, taskList, screenCoordinates, results, hitCheck, true  ) ||
+      HitTestRenderTaskList( sceneSize, layers, taskList, screenCoordinates, results, hitCheck, false ) )
   {
     // Found hit.
     result = true;
@@ -609,16 +611,15 @@ bool HitTestForEachRenderTask( Stage& stage,
 
 } // unnamed namespace
 
-bool HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
+HitTestInterface::~HitTestInterface() = default;
+
+bool HitTest( const Vector2& sceneSize, RenderTaskList& taskList, LayerList& layerList, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
 {
   bool wasHit( false );
-  // Hit-test the regular on-stage actors
-  RenderTaskList& taskList = stage.GetRenderTaskList();
-  LayerList& layerList = stage.GetLayerList();
-
+  // Hit-test the regular on-scene actors
   Results hitTestResults;
   HitTestFunctionWrapper hitTestFunctionWrapper( func );
-  if( HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
+  if( HitTestForEachRenderTask( sceneSize, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
   {
     results.actor = hitTestResults.actor;
     results.actorCoordinates = hitTestResults.actorCoordinates;
@@ -627,56 +628,24 @@ bool HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgor
   return wasHit;
 }
 
-bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface )
+bool HitTest( const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface )
 {
   bool wasHit( false );
 
-  // Hit-test the system-overlay actors first
-  SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal();
-
-  if( systemOverlay )
-  {
-    RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks();
-    LayerList& overlayLayerList = systemOverlay->GetLayerList();
-
-    wasHit = HitTestForEachRenderTask( stage, overlayLayerList, overlayTaskList, screenCoordinates, results, hitTestInterface );
-  }
-
-  // Hit-test the regular on-stage actors
+  // Hit-test the regular on-scene actors
   if( !wasHit )
   {
-    RenderTaskList& taskList = stage.GetRenderTaskList();
-    LayerList& layerList = stage.GetLayerList();
-
-    wasHit = HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, results, hitTestInterface );
+    wasHit = HitTestForEachRenderTask( sceneSize, layerList, renderTaskList, screenCoordinates, results, hitTestInterface );
   }
   return wasHit;
 }
 
-bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results )
+bool HitTest( const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results )
 {
   ActorTouchableCheck actorTouchableCheck;
-  return HitTest( stage, screenCoordinates, results, actorTouchableCheck );
+  return HitTest( sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck );
 }
 
-bool HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates,
-              Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
-{
-  bool wasHit( false );
-  Results hitTestResults;
-
-  const Vector< RenderTaskList::Exclusive >& exclusives = stage.GetRenderTaskList().GetExclusivesList();
-  HitTestFunctionWrapper hitTestFunctionWrapper( func );
-  if( HitTestRenderTask( exclusives, stage, stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
-  {
-    results.actor = hitTestResults.actor;
-    results.actorCoordinates = hitTestResults.actorCoordinates;
-    wasHit = true;
-  }
-  return wasHit;
-}
-
-
 } // namespace HitTestAlgorithm
 
 } // namespace Internal