943c6cdd7d97a6d15dd6e652d04429c1e5892185
[platform/core/uifw/dali-core.git] / dali / internal / event / events / hit-test-algorithm-impl.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
20
21 // INTERNAL INCLUDES
22 #include <dali/public-api/actors/layer.h>
23 #include <dali/public-api/math/vector2.h>
24 #include <dali/public-api/math/vector4.h>
25 #include <dali/integration-api/debug.h>
26 #include <dali/internal/event/actors/actor-impl.h>
27 #include <dali/internal/event/actors/camera-actor-impl.h>
28 #include <dali/internal/event/actors/layer-impl.h>
29 #include <dali/internal/event/actors/layer-list.h>
30 #include <dali/internal/event/common/projection.h>
31 #include <dali/internal/event/render-tasks/render-task-impl.h>
32 #include <dali/internal/event/render-tasks/render-task-list-impl.h>
33
34 namespace Dali
35 {
36
37 namespace Internal
38 {
39
40 namespace HitTestAlgorithm
41 {
42
43 namespace
44 {
45
46 struct HitActor
47 {
48   HitActor()
49   : actor( NULL ),
50     distance( std::numeric_limits<float>::max() ),
51     depth( std::numeric_limits<int>::min() )
52   {
53   }
54
55   Actor *actor;        ///< The actor hit (if actor is hit, then this is initialised).
56   Vector2 hitPosition; ///< Position of hit (only valid if actor valid).
57   float distance;      ///< Distance from ray origin to hit actor.
58   int32_t depth;       ///< Depth index of this actor.
59 };
60
61 /**
62  * Creates an Actor handle so that a HitTestFunction provided via the public API can be called.
63  */
64 struct HitTestFunctionWrapper : public HitTestInterface
65 {
66   /**
67    * Constructor
68    *
69    * @param[in] func HitTestFunction to call with an Actor handle.
70    */
71   HitTestFunctionWrapper( Dali::HitTestAlgorithm::HitTestFunction func )
72   : mFunc( func )
73   {
74   }
75
76   virtual bool IsActorHittable( Actor* actor )
77   {
78     return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::CHECK_ACTOR );
79   }
80
81   virtual bool DescendActorHierarchy( Actor* actor )
82   {
83     return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE );
84   }
85
86   virtual bool DoesLayerConsumeHit( Layer* layer )
87   {
88     // Layer::IsTouchConsumed() focuses on touch only. Here we are a wrapper for the public-api
89     // where the caller may want to check for something completely different.
90     // TODO: Should provide a means to let caller decide. For now do not allow layers to consume
91     return false;
92   }
93
94   Dali::HitTestAlgorithm::HitTestFunction mFunc;
95 };
96
97 /**
98  * Used in the hit-test algorithm to check whether the actor is touchable.
99  * It is used by the touch event processor.
100  */
101 struct ActorTouchableCheck : public HitTestInterface
102 {
103   virtual bool IsActorHittable( Actor* actor )
104   {
105     return actor->GetTouchRequired() && // Does the Application or derived actor type require a touch event?
106            actor->IsHittable();         // Is actor sensitive, visible and on the scene?
107   }
108
109   virtual bool DescendActorHierarchy( Actor* actor )
110   {
111     return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible.
112            actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either.
113   }
114
115   virtual bool DoesLayerConsumeHit( Layer* layer )
116   {
117     return layer->IsTouchConsumed();
118   }
119 };
120
121 /**
122  * Check to see if the actor we're about to hit test is exclusively owned by another rendertask?
123  */
124 bool IsActorExclusiveToAnotherRenderTask( const Actor& actor,
125                                           const RenderTask& renderTask,
126                                           const RenderTaskList::ExclusivesContainer& exclusives )
127
128 {
129   if( exclusives.size() )
130   {
131     for( const auto& exclusive : exclusives )
132     {
133       if( ( exclusive.renderTaskPtr != &renderTask ) && ( exclusive.actor.GetActor() == &actor ) )
134       {
135         return true;
136       }
137     }
138   }
139   return false;
140 }
141
142 /**
143  * Recursively hit test all the actors, without crossing into other layers.
144  * This algorithm performs a Depth-First-Search (DFS) on all Actors within Layer.
145  * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin
146  * of touch vector). The closest Hit-Tested Actor is that which is returned.
147  * Exceptions to this rule are:
148  * - When comparing against renderable parents, if Actor is the same distance
149  * or closer than it's renderable parent, then it takes priority.
150  */
151 HitActor HitTestWithinLayer( Actor& actor,
152                              const RenderTask& renderTask,
153                              const RenderTaskList::ExclusivesContainer& exclusives,
154                              const Vector4& rayOrigin,
155                              const Vector4& rayDir,
156                              float& nearClippingPlane,
157                              float& farClippingPlane,
158                              HitTestInterface& hitCheck,
159                              bool& overlayHit,
160                              bool layerIs3d,
161                              uint32_t clippingDepth,
162                              uint32_t clippingBitPlaneMask )
163 {
164   HitActor hit;
165
166   if( IsActorExclusiveToAnotherRenderTask( actor, renderTask, exclusives ) )
167   {
168     return hit;
169   }
170
171   // For clipping, regardless of whether we have hit this actor or not,
172   // we increase the clipping depth if we have hit a clipping actor.
173   // This is used later to ensure all nested clipped children have hit
174   // all clipping actors also for them to be counted as hit.
175   uint32_t newClippingDepth = clippingDepth;
176   bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED;
177   if( clippingActor )
178   {
179     ++newClippingDepth;
180   }
181
182   // If we are a clipping actor or hittable...
183   if( clippingActor || hitCheck.IsActorHittable( &actor ) )
184   {
185     Vector3 size( actor.GetCurrentSize() );
186
187     // Ensure the actor has a valid size.
188     // If so, perform a quick ray sphere test to see if our ray is close to the actor.
189     if( size.x > 0.0f && size.y > 0.0f && actor.RaySphereTest( rayOrigin, rayDir ) )
190     {
191       Vector2 hitPointLocal;
192       float distance;
193
194       // Finally, perform a more accurate ray test to see if our ray actually hits the actor.
195       if( actor.RayActorTest( rayOrigin, rayDir, hitPointLocal, distance ) )
196       {
197         if( distance >= nearClippingPlane && distance <= farClippingPlane )
198         {
199           // If the hit has happened on a clipping actor, then add this clipping depth to the mask of hit clipping depths.
200           // This mask shows all the actors that have been hit at different clipping depths.
201           if( clippingActor )
202           {
203             clippingBitPlaneMask |= 1u << clippingDepth;
204           }
205
206           if( overlayHit && !actor.IsOverlay() )
207           {
208             // If we have already hit an overlay and current actor is not an overlay ignore current actor.
209           }
210           else
211           {
212             if( actor.IsOverlay() )
213             {
214               overlayHit = true;
215             }
216
217             // At this point we have hit an actor.
218             // Now perform checks for clipping.
219             // Assume we have hit the actor first as if it is not clipped this would be the case.
220             bool haveHitActor = true;
221
222             // Check if we are performing clipping. IE. if any actors so far have clipping enabled - not necessarily this one.
223             // We can do this by checking the clipping depth has a value 1 or above.
224             if( newClippingDepth >= 1u )
225             {
226               // Now for us to count this actor as hit, we must have also hit
227               // all CLIPPING actors up to this point in the hierarchy as well.
228               // This information is stored in the clippingBitPlaneMask we updated above.
229               // Here we calculate a comparison mask by setting all the bits up to the current depth value.
230               // EG. a depth of 4 (10000 binary) = a mask of 1111 binary.
231               // This allows us a fast way of comparing all bits are set up to this depth.
232               // Note: If the current Actor has clipping, that is included in the depth mask too.
233               uint32_t clippingDepthMask = ( 1u << newClippingDepth ) - 1u;
234
235               // The two masks must be equal to be a hit, as we are already assuming a hit
236               // (for non-clipping mode) then they must be not-equal to disqualify the hit.
237               if( clippingBitPlaneMask != clippingDepthMask )
238               {
239                 haveHitActor = false;
240               }
241             }
242
243             if( haveHitActor )
244             {
245               hit.actor = &actor;
246               hit.hitPosition = hitPointLocal;
247               hit.distance = distance;
248               hit.depth = actor.GetSortingDepth() ;
249
250               if( actor.GetRendererCount() > 0 )
251               {
252                 //Get renderer with maximum depth
253                 int rendererMaxDepth(actor.GetRendererAt( 0 ).Get()->GetDepthIndex());
254                 for( uint32_t i(1); i < actor.GetRendererCount(); ++i )
255                 {
256                   int depth = actor.GetRendererAt( i ).Get()->GetDepthIndex();
257                   if( depth > rendererMaxDepth )
258                   {
259                     rendererMaxDepth = depth;
260                   }
261                 }
262                 hit.depth += rendererMaxDepth;
263               }
264             }
265           }
266         }
267       }
268     }
269   }
270
271   // Find a child hit, until we run out of actors in the current layer.
272   HitActor childHit;
273   if( actor.GetChildCount() > 0 )
274   {
275     childHit.distance = std::numeric_limits<float>::max();
276     childHit.depth = std::numeric_limits<int32_t>::min();
277     ActorContainer& children = actor.GetChildrenInternal();
278
279     // Hit test ALL children and calculate their distance.
280     bool parentIsRenderable = actor.IsRenderable();
281
282     for( ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter )
283     {
284       // Descend tree only if...
285       if ( !( *iter )->IsLayer() &&                                 // Child is NOT a layer, hit testing current layer only
286             ( hitCheck.DescendActorHierarchy( ( *iter ).Get() ) ) ) // We can descend into child hierarchy
287       {
288         HitActor currentHit( HitTestWithinLayer( ( *iter->Get() ),
289                                                  renderTask,
290                                                  exclusives,
291                                                  rayOrigin,
292                                                  rayDir,
293                                                  nearClippingPlane,
294                                                  farClippingPlane,
295                                                  hitCheck,
296                                                  overlayHit,
297                                                  layerIs3d,
298                                                  newClippingDepth,
299                                                  clippingBitPlaneMask ) );
300
301         bool updateChildHit = false;
302         if( currentHit.distance >= 0.0f )
303         {
304           if( layerIs3d )
305           {
306             updateChildHit = ( ( currentHit.depth > childHit.depth ) ||
307                                ( ( currentHit.depth == childHit.depth ) && ( currentHit.distance < childHit.distance ) ) );
308           }
309           else
310           {
311             updateChildHit = currentHit.depth >= childHit.depth;
312           }
313         }
314
315         if( updateChildHit )
316         {
317           if( !parentIsRenderable || currentHit.depth > hit.depth ||
318             ( layerIs3d && ( currentHit.depth == hit.depth && currentHit.distance < hit.distance )) )
319           {
320             childHit = currentHit;
321           }
322         }
323       }
324     }
325   }
326
327   return ( childHit.actor ) ? childHit : hit;
328 }
329
330 /**
331  * Return true if actor is sourceActor or a descendent of sourceActor
332  */
333 bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor )
334 {
335   if ( &sourceActor == &actor )
336   {
337     return true;
338   }
339
340   Actor* parent = actor.GetParent();
341   if ( parent )
342   {
343     return IsWithinSourceActors( sourceActor, *parent );
344   }
345
346   // Not within source actors
347   return false;
348 }
349
350 /**
351  * Returns true if the layer and all of the layer's parents are visible and sensitive.
352  */
353 inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck )
354 {
355   bool hittable( true );
356
357   if( layer.IsClipping() )
358   {
359     ClippingBox box = layer.GetClippingBox();
360
361     if( screenCoordinates.x < static_cast<float>( box.x )||
362         screenCoordinates.x > static_cast<float>( box.x + box.width )||
363         screenCoordinates.y < stageSize.y - static_cast<float>( box.y + box.height ) ||
364         screenCoordinates.y > stageSize.y - static_cast<float>( box.y ) )
365     {
366       // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
367       hittable = false;
368     }
369   }
370
371   if( hittable )
372   {
373     Actor* actor( &layer );
374
375     // Ensure that we can descend into the layer's (or any of its parent's) hierarchy.
376     while( actor && hittable )
377     {
378       if( ! hitCheck.DescendActorHierarchy( actor ) )
379       {
380         hittable = false;
381         break;
382       }
383       actor = actor->GetParent();
384     }
385   }
386
387   return hittable;
388 }
389
390 /**
391  * Gets the near and far clipping planes of the camera from which the scene is viewed in the render task.
392  */
393 void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane )
394 {
395   CameraActor* cameraActor = renderTask.GetCameraActor();
396   nearClippingPlane = cameraActor->GetNearClippingPlane();
397   farClippingPlane = cameraActor->GetFarClippingPlane();
398 }
399
400 /**
401  * Hit test a RenderTask
402  */
403 bool HitTestRenderTask( const RenderTaskList::ExclusivesContainer& exclusives,
404                         const Vector2& sceneSize,
405                         LayerList& layers,
406                         RenderTask& renderTask,
407                         Vector2 screenCoordinates,
408                         Results& results,
409                         HitTestInterface& hitCheck )
410 {
411   if ( renderTask.IsHittable( screenCoordinates ) )
412   {
413     Viewport viewport;
414     renderTask.GetViewport( viewport );
415     if( screenCoordinates.x < static_cast<float>( viewport.x ) ||
416         screenCoordinates.x > static_cast<float>( viewport.x + viewport.width ) ||
417         screenCoordinates.y < static_cast<float>( viewport.y ) ||
418         screenCoordinates.y > static_cast<float>( viewport.y + viewport.height ) )
419     {
420       // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
421       return false;
422     }
423
424     float nearClippingPlane, farClippingPlane;
425     GetCameraClippingPlane( renderTask, nearClippingPlane, farClippingPlane );
426
427     // Determine the layer depth of the source actor
428     Actor* sourceActor( renderTask.GetSourceActor() );
429     if( sourceActor )
430     {
431       Dali::Layer layer( sourceActor->GetLayer() );
432       if( layer )
433       {
434         const uint32_t sourceActorDepth( layer.GetProperty< bool >( Dali::Layer::Property::DEPTH ) );
435
436         CameraActor* cameraActor = renderTask.GetCameraActor();
437         bool pickingPossible = cameraActor->BuildPickingRay(
438             screenCoordinates,
439             viewport,
440             results.rayOrigin,
441             results.rayDirection );
442         if( !pickingPossible )
443         {
444           return false;
445         }
446
447         // Hit test starting with the top layer, working towards the bottom layer.
448         HitActor hit;
449         bool overlayHit = false;
450         bool layerConsumesHit = false;
451
452         for( int32_t i = layers.GetLayerCount() - 1; i >= 0 && !( hit.actor ); --i )
453         {
454           Layer* layer( layers.GetLayer( i ) );
455           overlayHit = false;
456
457           // Ensure layer is touchable (also checks whether ancestors are also touchable)
458           if( IsActuallyHittable( *layer, screenCoordinates, sceneSize, hitCheck ) )
459           {
460             // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
461             if( sourceActorDepth == static_cast<uint32_t>( i ) )
462             {
463               // Recursively hit test the source actor & children, without crossing into other layers.
464               hit = HitTestWithinLayer( *sourceActor,
465                                         renderTask,
466                                         exclusives,
467                                         results.rayOrigin,
468                                         results.rayDirection,
469                                         nearClippingPlane,
470                                         farClippingPlane,
471                                         hitCheck,
472                                         overlayHit,
473                                         layer->GetBehavior() == Dali::Layer::LAYER_3D,
474                                         0u,
475                                         0u );
476             }
477             else if( IsWithinSourceActors( *sourceActor, *layer ) )
478             {
479               // Recursively hit test all the actors, without crossing into other layers.
480               hit = HitTestWithinLayer( *layer,
481                                         renderTask,
482                                         exclusives,
483                                         results.rayOrigin,
484                                         results.rayDirection,
485                                         nearClippingPlane,
486                                         farClippingPlane,
487                                         hitCheck,
488                                         overlayHit,
489                                         layer->GetBehavior() == Dali::Layer::LAYER_3D,
490                                         0u,
491                                         0u );
492             }
493
494             // If this layer is set to consume the hit, then do not check any layers behind it
495             if( hitCheck.DoesLayerConsumeHit( layer ) )
496             {
497               layerConsumesHit = true;
498               break;
499             }
500           }
501         }
502
503         if( hit.actor )
504         {
505           results.renderTask = RenderTaskPtr( &renderTask );
506           results.actor = Dali::Actor( hit.actor );
507           results.actorCoordinates = hit.hitPosition;
508
509           return true; // Success
510         }
511
512         if( layerConsumesHit )
513         {
514           return true; // Also success if layer is consuming the hit
515         }
516       }
517     }
518   }
519   return false;
520 }
521
522 /**
523  * Iterate through the RenderTaskList and perform hit testing.
524  *
525  * @param[in] sceneSize The scene size the tests will be performed in
526  * @param[in] layers The list of layers to test
527  * @param[in] taskList The list of render tasks
528  * @param[out] results Ray information calculated by the camera
529  * @param[in] hitCheck The hit testing interface object to use
530  * @param[in] onScreen True to test on-screen, false to test off-screen
531  * @return True if we have a hit, false otherwise
532  */
533 bool HitTestRenderTaskList( const Vector2& sceneSize,
534                             LayerList& layers,
535                             RenderTaskList& taskList,
536                             const Vector2& screenCoordinates,
537                             Results& results,
538                             HitTestInterface& hitCheck,
539                             bool onScreen )
540 {
541   RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
542   RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
543   const auto& exclusives = taskList.GetExclusivesList();
544
545   for( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
546   {
547     RenderTask& renderTask = *iter->Get();
548     const bool isOffscreenRenderTask = renderTask.GetFrameBuffer();
549     if( (onScreen && isOffscreenRenderTask) || (!onScreen && !isOffscreenRenderTask) )
550     {
551       // Skip to next task
552       continue;
553     }
554
555     if( HitTestRenderTask( exclusives, sceneSize, layers, renderTask, screenCoordinates, results, hitCheck ) )
556     {
557       // Return true when an actor is hit (or layer in our render-task consumes the hit)
558       return true; // don't bother checking off screen tasks
559     }
560   }
561
562   return false;
563 }
564
565 /**
566  * Iterate through the RenderTaskList and perform hit testing for both on-screen and off-screen.
567  *
568  * @param[in] sceneSize The scene size the tests will be performed in
569  * @param[in] layers The list of layers to test
570  * @param[in] taskList The list of render tasks
571  * @param[out] results Ray information calculated by the camera
572  * @param[in] hitCheck The hit testing interface object to use
573  * @param[in] onScreen True to test on-screen, false to test off-screen
574  * @return True if we have a hit, false otherwise
575  */
576 bool HitTestForEachRenderTask( const Vector2& sceneSize,
577                                LayerList& layers,
578                                RenderTaskList& taskList,
579                                const Vector2& screenCoordinates,
580                                Results& results,
581                                HitTestInterface& hitCheck )
582 {
583   bool result = false;
584
585   // Check on-screen tasks before off-screen ones.
586   // Hit test order should be reverse of draw order (see ProcessRenderTasks() where off-screen tasks are drawn first).
587   if( HitTestRenderTaskList( sceneSize, layers, taskList, screenCoordinates, results, hitCheck, true  ) ||
588       HitTestRenderTaskList( sceneSize, layers, taskList, screenCoordinates, results, hitCheck, false ) )
589   {
590     // Found hit.
591     result = true;
592   }
593
594   return result;
595 }
596
597 } // unnamed namespace
598
599 HitTestInterface::~HitTestInterface()
600 {
601 }
602
603 bool HitTest( const Vector2& sceneSize, RenderTaskList& taskList, LayerList& layerList, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
604 {
605   bool wasHit( false );
606   // Hit-test the regular on-scene actors
607   Results hitTestResults;
608   HitTestFunctionWrapper hitTestFunctionWrapper( func );
609   if( HitTestForEachRenderTask( sceneSize, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
610   {
611     results.actor = hitTestResults.actor;
612     results.actorCoordinates = hitTestResults.actorCoordinates;
613     wasHit = true;
614   }
615   return wasHit;
616 }
617
618 bool HitTest( const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface )
619 {
620   bool wasHit( false );
621
622   // Hit-test the regular on-scene actors
623   if( !wasHit )
624   {
625     wasHit = HitTestForEachRenderTask( sceneSize, layerList, renderTaskList, screenCoordinates, results, hitTestInterface );
626   }
627   return wasHit;
628 }
629
630 bool HitTest( const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results )
631 {
632   ActorTouchableCheck actorTouchableCheck;
633   return HitTest( sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck );
634 }
635
636 } // namespace HitTestAlgorithm
637
638 } // namespace Internal
639
640 } // namespace Dali