Optimize HitTestWithinLayer (20% faster HitTest).
[platform/core/uifw/dali-core.git] / dali / internal / event / events / hit-test-algorithm-impl.cpp
1 //
2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
3 //
4 // Licensed under the Flora License, Version 1.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://floralicense.org/license/
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 // CLASS HEADER
18 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
19
20 // INTERNAL INCLUDES
21 #include <dali/integration-api/system-overlay.h>
22 #include <dali/public-api/math/vector2.h>
23 #include <dali/public-api/math/vector4.h>
24 #include <dali/integration-api/debug.h>
25 #include <dali/internal/event/actors/actor-impl.h>
26 #include <dali/internal/event/actors/camera-actor-impl.h>
27 #include <dali/internal/event/actors/layer-impl.h>
28 #include <dali/internal/event/actors/layer-list.h>
29 #include <dali/internal/event/actors/renderable-actor-impl.h>
30 #include <dali/internal/event/common/system-overlay-impl.h>
31 #include <dali/internal/event/common/stage-impl.h>
32 #include <dali/internal/event/common/projection.h>
33 #include <dali/internal/event/render-tasks/render-task-impl.h>
34 #include <dali/internal/event/render-tasks/render-task-list-impl.h>
35
36 namespace Dali
37 {
38
39 namespace Internal
40 {
41
42 namespace HitTestAlgorithm
43 {
44
45 namespace
46 {
47
48 struct HitActor
49 {
50   HitActor()
51   : actor( NULL ),
52     x( 0 ),
53     y( 0 ),
54     distance( std::numeric_limits<float>::max() ),
55     overlay( false )
56   {
57
58   }
59
60   Actor *actor;                         ///< the actor hit. (if actor hit, then initialised)
61   float x;                              ///< x position of hit (only valid if actor valid)
62   float y;                              ///< y position of hit (only valid if actor valid)
63   float distance;                       ///< distance from ray origin to hit actor
64   bool overlay;                         ///< true if the hit actor is an overlay
65
66 };
67
68 /**
69  * The function to be used in the hit-test algorithm to check whether the actor is touchable.
70  * It is used by the touch event and gesture processor.
71  */
72 bool IsActorTouchableFunction(Dali::Actor actor, Dali::HitTestAlgorithm::TraverseType type)
73 {
74   bool hittable = false;
75
76   switch (type)
77   {
78     case Dali::HitTestAlgorithm::CHECK_ACTOR:
79     {
80       if( GetImplementation(actor).GetTouchRequired() && // Does the Application or derived actor type require a touch event?
81           GetImplementation(actor).IsHittable() ) // Is actor sensitive, visible and on the scene?
82       {
83         hittable = true;
84       }
85       break;
86     }
87     case Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE:
88     {
89       if( actor.IsVisible() &&     // Actor is visible, if not visible then none of its children are visible.
90           actor.IsSensitive() )    // Actor is sensitive, if insensitive none of its children should be hittable either.
91       {
92         hittable = true;
93       }
94       break;
95     }
96     default:
97     {
98       break;
99     }
100   }
101
102   return hittable;
103 }
104
105 /**
106  * Recursively hit test all the actors, without crossing into other layers.
107  * This algorithm performs a Depth-First-Search (DFS) on all Actors within Layer.
108  * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin
109  * of touch vector). The closest Hit-Tested Actor is that which is returned.
110  * Exceptions to this rule are:
111  * - If the Actor is an overlay then it is considered closer than all previous
112  * overlays encountered in the hit test traversal.
113  * - When comparing against renderable parents, if Actor is the same distance
114  * or closer than it's renderable parent, then it takes priority.
115  */
116 HitActor HitTestWithinLayer( Actor& actor,
117                              const Vector4& rayOrigin,
118                              const Vector4& rayDir,
119                              bool worldOverlay,
120                              float& nearClippingPlane,
121                              float& farClippingPlane,
122                              Dali::HitTestAlgorithm::HitTestFunction func,
123                              bool& stencilOnLayer,
124                              bool& stencilHit,
125                              bool parentIsStencil )
126 {
127   worldOverlay |= actor.IsOverlay();
128
129   HitActor hit;
130
131   // Children should inherit the stencil draw mode
132   bool isStencil = parentIsStencil;
133
134   if ( actor.GetDrawMode() == DrawMode::STENCIL && actor.IsVisible() )
135   {
136     isStencil = true;
137     stencilOnLayer = true;
138   }
139
140   // If we are a stencil or hittable...
141   if ( isStencil || func(Dali::Actor(&actor), Dali::HitTestAlgorithm::CHECK_ACTOR) ) // Is actor hittable
142   {
143     Vector3 size( actor.GetCurrentSize() );
144
145     if ( size.x > 0.0f && size.y > 0.0f &&              // Ensure the actor has a valid size.
146          actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor.
147     {
148       Vector4 hitPointLocal;
149       float distance;
150
151       // Finally, perform a more accurate ray test to see if our ray actually hits the actor.
152       if( actor.RayActorTest( rayOrigin, rayDir, hitPointLocal, distance ) )
153       {
154         if( distance >= nearClippingPlane && distance <= farClippingPlane )
155         {
156           // If the hit has happened on a stencil then register, but don't record as hit result
157           if ( isStencil )
158           {
159             stencilHit = true;
160           }
161           else
162           {
163             hit.actor = &actor;
164             hit.x = hitPointLocal.x;
165             hit.y = hitPointLocal.y;
166             hit.distance = distance;
167             hit.overlay = worldOverlay;
168           }
169         }
170       }
171     }
172   }
173
174   // If there is a stencil on this layer and we've also registered a hit, then don't both searching any children
175   if ( stencilHit && hit.actor )
176   {
177     return hit;
178   }
179
180   // Find a child hit, until we run out of actors in the current layer.
181   HitActor childHit;
182   if( actor.GetChildCount() > 0 )
183   {
184     childHit.distance = std::numeric_limits<float>::max();
185     Dali::ActorContainer& children = actor.GetChildrenInternal();
186
187     // Hit test ALL children and calculate their distance.
188     bool parentIsRenderable = actor.IsRenderable();
189
190     for (Dali::ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter)
191     {
192       // Descend tree only if...
193       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
194            ( isStencil || func(*iter, Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE ) ) ) // Child is visible and sensitive, otherwise none of its children should be hittable.
195       {
196         HitActor currentHit( HitTestWithinLayer( GetImplementation(*iter), rayOrigin, rayDir, worldOverlay, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, isStencil ) );
197
198         // If Current child is an overlay, then it takes priority.
199         // If it is not an overlay, and the previously hit sibling is also not an overlay, then closest takes priority.
200         // (last overlay sibling has priority as is rendered on top)
201         if ( currentHit.distance >= 0.f && (currentHit.overlay || (!childHit.overlay && currentHit.distance < childHit.distance) ) )
202         {
203           if ( !parentIsRenderable )
204           {
205             // If our parent is not renderable, then child should be hit regardless of distance.
206             childHit = currentHit;
207           }
208           else if ( currentHit.overlay || (!hit.overlay && currentHit.distance <= hit.distance) )
209           {
210             // 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.
211             // (child overlay has priority as is rendered on top of it's parent)
212             childHit = currentHit;
213           }
214         }
215       }
216     }
217   }
218   return ( childHit.actor ) ? childHit : hit;
219 }
220
221 /**
222  * Return true if actor is sourceActor or a descendent of sourceActor
223  */
224 bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor )
225 {
226   if ( &sourceActor == &actor )
227   {
228     return true;
229   }
230   else
231   {
232     Actor* parent = actor.GetParent();
233     if ( parent )
234     {
235       return IsWithinSourceActors( sourceActor, *parent );
236     }
237   }
238
239   // Not within source actors
240   return false;
241 }
242
243 /**
244  * Returns true if the layer and all of the layer's parents are visible and sensitive.
245  */
246 inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, Dali::HitTestAlgorithm::HitTestFunction func )
247 {
248   bool hittable( true );
249
250   if(layer.IsClipping())
251   {
252     ClippingBox box = layer.GetClippingBox();
253
254     if( screenCoordinates.x < box.x ||
255         screenCoordinates.x > box.x + box.width ||
256         screenCoordinates.y < stageSize.y - (box.y + box.height) ||
257         screenCoordinates.y > stageSize.y - box.y)
258     {
259       // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
260       hittable = false;
261     }
262   }
263
264   if(hittable)
265   {
266     Actor* actor( &layer );
267     while ( actor && hittable )
268     {
269       if ( !(func(Dali::Actor(actor), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE)) ) // Layer (or its Parent) is NOT visible and sensitive, so our layer is not either.
270       {
271         hittable = false;
272         break;
273       }
274       actor = actor->GetParent();
275     }
276   }
277
278   return hittable;
279 }
280
281 /**
282  * Gets the near and far clipping planes of the camera from which the scene is viewed in the render task.
283  */
284 void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane )
285 {
286   CameraActor* cameraActor = renderTask.GetCameraActor();
287   nearClippingPlane = cameraActor->GetNearClippingPlane();
288   farClippingPlane = cameraActor->GetFarClippingPlane();
289 }
290
291 /**
292  * Hit test a RenderTask
293  */
294 bool HitTestRenderTask( LayerList& layers,
295                         RenderTask& renderTask,
296                         Vector2 screenCoordinates,
297                         Results& results,
298                         Dali::HitTestAlgorithm::HitTestFunction func )
299 {
300   if ( renderTask.IsHittable( screenCoordinates ) )
301   {
302     Viewport viewport;
303     renderTask.GetViewport( viewport );
304     if( screenCoordinates.x < viewport.x ||
305         screenCoordinates.x > viewport.x + viewport.width ||
306         screenCoordinates.y < viewport.y ||
307         screenCoordinates.y > viewport.y + viewport.height )
308     {
309       // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
310       return false;
311     }
312
313     float nearClippingPlane, farClippingPlane;
314     GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane);
315
316     // Determine the layer depth of the source actor
317     Actor* sourceActor( renderTask.GetSourceActor() );
318     if ( sourceActor )
319     {
320       Dali::Layer layer( sourceActor->GetLayer() );
321       if ( layer )
322       {
323         const unsigned int sourceActorDepth( layer.GetDepth() );
324
325         CameraActor* cameraActor = renderTask.GetCameraActor();
326         bool pickingPossible = cameraActor->BuildPickingRay(
327             screenCoordinates,
328             viewport,
329             results.rayOrigin,
330             results.rayDirection );
331         if( !pickingPossible )
332         {
333           return false;
334         }
335
336         // Hit test starting with the top layer, working towards the bottom layer.
337         HitActor hit;
338         bool stencilOnLayer = false;
339         bool stencilHit = false;
340         const Vector2& stageSize = Stage::GetCurrent()->GetSize();
341
342         for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i)
343         {
344           Layer* layer( layers.GetLayer(i) );
345
346           HitActor previousHit = hit;
347           stencilOnLayer = false;
348           stencilHit = false;
349
350           // Ensure layer is touchable (also checks whether ancestors are also touchable)
351           if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, func ) )
352           {
353             // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
354             if ( sourceActorDepth == static_cast<unsigned int>(i) )
355             {
356               // Recursively hit test the source actor & children, without crossing into other layers.
357               hit = HitTestWithinLayer( *sourceActor, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false );
358             }
359             else if ( IsWithinSourceActors( *sourceActor, *layer ) )
360             {
361               // Recursively hit test all the actors, without crossing into other layers.
362               hit = HitTestWithinLayer( *layer, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false );
363             }
364             // If a stencil on this layer hasn't been hit, then discard hit results for this layer
365             if ( stencilOnLayer && !stencilHit )
366             {
367              hit = previousHit;
368             }
369           }
370         }
371         if ( hit.actor )
372         {
373           results.renderTask = Dali::RenderTask(&renderTask);
374           results.actor = Dali::Actor(hit.actor);
375           results.actorCoordinates.x = hit.x;
376           results.actorCoordinates.y = hit.y;
377           return true; // Success
378         }
379       }
380     }
381   }
382   return false;
383 }
384
385 /**
386  * Iterate through RenderTaskList and perform hit test.
387  */
388
389 void HitTestForEachRenderTask( LayerList& layers,
390                                RenderTaskList& taskList,
391                                const Vector2& screenCoordinates,
392                                Results& results,
393                                Dali::HitTestAlgorithm::HitTestFunction func )
394 {
395   RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
396   RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
397
398   // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first).
399
400   // on screen
401   for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
402   {
403     RenderTask& renderTask = GetImplementation( *iter );
404     Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
405
406     // Note that if frameBufferImage is NULL we are using the default (on screen) render target
407     if(frameBufferImage)
408     {
409       ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
410
411       // on screen only
412       if(0 != id)
413       {
414         // Skip to next task
415         continue;
416       }
417     }
418
419     if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) )
420     {
421       // Exit when an actor is hit
422       return; // don't bother checking off screen tasks
423     }
424   }
425
426   // off screen
427   for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
428   {
429     RenderTask& renderTask = GetImplementation( *iter );
430     Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
431
432     // Note that if frameBufferImage is NULL we are using the default (on screen) render target
433     if(frameBufferImage)
434     {
435       ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
436
437       // off screen only
438       if(0 == id)
439       {
440         // Skip to next task
441         continue;
442       }
443
444       if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) )
445       {
446         // Exit when an actor is hit
447         break;
448       }
449     }
450   }
451 }
452
453 } // unnamed namespace
454
455 void HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
456 {
457   // Hit-test the regular on-stage actors
458   RenderTaskList& taskList = stage.GetRenderTaskList();
459   LayerList& layerList = stage.GetLayerList();
460
461   Results hitTestResults;
462   HitTestForEachRenderTask( layerList, taskList, screenCoordinates, hitTestResults, func );
463
464   results.actor = hitTestResults.actor;
465   results.actorCoordinates = hitTestResults.actorCoordinates;
466 }
467
468 void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results )
469 {
470   // Hit-test the system-overlay actors first
471   SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal();
472
473   if ( systemOverlay )
474   {
475     RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks();
476     LayerList& overlayLayerList = systemOverlay->GetLayerList();
477
478     HitTestForEachRenderTask( overlayLayerList, overlayTaskList, screenCoordinates, results, IsActorTouchableFunction );
479   }
480
481   // Hit-test the regular on-stage actors
482   if ( !results.actor )
483   {
484     RenderTaskList& taskList = stage.GetRenderTaskList();
485     LayerList& layerList = stage.GetLayerList();
486
487     HitTestForEachRenderTask( layerList, taskList, screenCoordinates, results, IsActorTouchableFunction );
488   }
489 }
490
491 void HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates,
492               Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
493 {
494   Results hitTestResults;
495   HitTestRenderTask( stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, func );
496   results.actor = hitTestResults.actor;
497   results.actorCoordinates = hitTestResults.actorCoordinates;
498 }
499
500 } // namespace HitTestAlgorithm
501
502 } // namespace Internal
503
504 } // namespace Dali