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