Dali 3D User Interface Engine
hit-test-algorithm-impl.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2014 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
20 
21 // INTERNAL INCLUDES
38 
39 namespace Dali
40 {
41 
42 namespace Internal
43 {
44 
45 namespace HitTestAlgorithm
46 {
47 
48 namespace
49 {
50 
51 struct HitActor
52 {
54  : actor( NULL ),
55  x( 0 ),
56  y( 0 ),
57  distance( std::numeric_limits<float>::max() ),
58  depth( std::numeric_limits<int>::min() )
59  {
60  }
61 
63  float x;
64  float y;
65  float distance;
66  int depth;
67 
68 };
69 
74 {
81  : mFunc( func )
82  {
83  }
84 
85  virtual bool IsActorHittable( Actor* actor )
86  {
87  return mFunc( Dali::Actor( actor ), Dali::HitTestAlgorithm::CHECK_ACTOR );
88  }
89 
90  virtual bool DescendActorHierarchy( Actor* actor )
91  {
93  }
94 
95  virtual bool DoesLayerConsumeHit( Layer* layer )
96  {
97  // Layer::IsTouchConsumed() focuses on touch only. Here we are a wrapper for the public-api
98  // where the caller may want to check for something completely different.
99  // TODO: Should provide a means to let caller decide. For now do not allow layers to consume
100  return false;
101  }
102 
104 };
105 
111 {
112  virtual bool IsActorHittable( Actor* actor )
113  {
114  return actor->GetTouchRequired() && // Does the Application or derived actor type require a touch event?
115  actor->IsHittable(); // Is actor sensitive, visible and on the scene?
116  }
117 
118  virtual bool DescendActorHierarchy( Actor* actor )
119  {
120  return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible.
121  actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either.
122  }
123 
124  virtual bool DoesLayerConsumeHit( Layer* layer )
125  {
126  return layer->IsTouchConsumed();
127  }
128 };
129 
134  const RenderTask& renderTask,
135  const Vector< RenderTaskList::Exclusive >& exclusives )
136 
137 {
138  if ( exclusives.Size() )
139  {
140  for ( Vector< RenderTaskList::Exclusive >::Iterator exclusiveIt = exclusives.Begin(); exclusives.End() != exclusiveIt; ++exclusiveIt )
141  {
142  if ( exclusiveIt->renderTaskPtr != &renderTask )
143  {
144  if ( exclusiveIt->actorPtr == &actor )
145  {
146  return true;
147  }
148  }
149  }
150  }
151  return false;
152 }
153 
164  const RenderTask& renderTask,
165  const Vector< RenderTaskList::Exclusive >& exclusives,
166  const Vector4& rayOrigin,
167  const Vector4& rayDir,
168  float& nearClippingPlane,
169  float& farClippingPlane,
170  HitTestInterface& hitCheck,
171  bool& stencilOnLayer,
172  bool& stencilHit,
173  bool& overlayHit,
174  bool parentIsStencil,
175  bool layerIs3d )
176 {
177  HitActor hit;
178 
179  if ( IsActorExclusiveToAnotherRenderTask( actor, renderTask, exclusives ) )
180  {
181  return hit;
182  }
183 
184  // Children should inherit the stencil draw mode
185  bool isStencil = parentIsStencil;
186 
187  if ( actor.GetDrawMode() == DrawMode::STENCIL && actor.IsVisible() )
188  {
189  isStencil = true;
190  stencilOnLayer = true;
191  }
192 
193  // If we are a stencil or hittable...
194  if ( isStencil || hitCheck.IsActorHittable( &actor ) )
195  {
196  Vector3 size( actor.GetCurrentSize() );
197 
198  if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size.
199  actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor.
200  {
201  Vector4 hitPointLocal;
202  float distance;
203 
204  // Finally, perform a more accurate ray test to see if our ray actually hits the actor.
205  if( actor.RayActorTest( rayOrigin, rayDir, hitPointLocal, distance ) )
206  {
207  if( distance >= nearClippingPlane && distance <= farClippingPlane )
208  {
209  // If the hit has happened on a stencil then register, but don't record as hit result
210  if ( isStencil )
211  {
212  stencilHit = true;
213  }
214  else
215  {
216  if( overlayHit && !actor.IsOverlay() )
217  {
218  //If we have already hit an overlay and current actor is not an overlay
219  //ignore current actor
220  }
221  else
222  {
223  if( actor.IsOverlay() )
224  {
225  overlayHit = true;
226  }
227 
228  hit.actor = &actor;
229  hit.x = hitPointLocal.x;
230  hit.y = hitPointLocal.y;
231  hit.distance = distance;
233 
234  if ( actor.GetRendererCount() > 0 )
235  {
236  //Get renderer with maximum depth
237  int rendererMaxDepth(actor.GetRendererAt( 0 ).Get()->GetDepthIndex());
238  for( unsigned int i(1); i<actor.GetRendererCount(); ++i)
239  {
240  int depth = actor.GetRendererAt( i ).Get()->GetDepthIndex();
241  if( depth > rendererMaxDepth )
242  {
243  rendererMaxDepth = depth;
244  }
245  }
246  hit.depth += rendererMaxDepth;
247  }
248  }
249  }
250  }
251  }
252  }
253  }
254 
255  // If we are a stencil (or a child of a stencil) and we have already ascertained that the stencil has been hit then there is no need to hit-test the children of this stencil-actor
256  if ( isStencil && stencilHit )
257  {
258  return hit;
259  }
260 
261  // Find a child hit, until we run out of actors in the current layer.
262  HitActor childHit;
263  if( actor.GetChildCount() > 0 )
264  {
265  childHit.distance = std::numeric_limits<float>::max();
267  ActorContainer& children = actor.GetChildrenInternal();
268 
269  // Hit test ALL children and calculate their distance.
270  bool parentIsRenderable = actor.IsRenderable();
271 
272  for( ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter )
273  {
274  // Descend tree only if...
275  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
276  ( isStencil || hitCheck.DescendActorHierarchy( ( *iter ).Get() ) ) ) // We are a stencil OR we can descend into child hierarchy
277  {
278  HitActor currentHit( HitTestWithinLayer( (*iter->Get()),
279  renderTask,
280  exclusives,
281  rayOrigin,
282  rayDir,
283  nearClippingPlane,
284  farClippingPlane,
285  hitCheck,
286  stencilOnLayer,
287  stencilHit,
288  overlayHit,
289  isStencil,
290  layerIs3d) );
291 
292  bool updateChildHit = false;
293  if ( currentHit.distance >= 0.0f )
294  {
295  if( layerIs3d )
296  {
297  updateChildHit = ( ( currentHit.depth > childHit.depth ) ||
298  ( ( currentHit.depth == childHit.depth ) && ( currentHit.distance < childHit.distance ) ) );
299  }
300  else
301  {
302  updateChildHit = currentHit.depth >= childHit.depth;
303  }
304  }
305 
306  if ( updateChildHit )
307  {
308  if( !parentIsRenderable || currentHit.depth > hit.depth ||
309  ( layerIs3d && ( currentHit.depth == hit.depth && currentHit.distance < hit.distance )) )
310  {
311  childHit = currentHit;
312  }
313  }
314  }
315  }
316  }
317  return ( childHit.actor ) ? childHit : hit;
318 }
319 
323 bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor )
324 {
325  if ( &sourceActor == &actor )
326  {
327  return true;
328  }
329  else
330  {
331  Actor* parent = actor.GetParent();
332  if ( parent )
333  {
334  return IsWithinSourceActors( sourceActor, *parent );
335  }
336  }
337 
338  // Not within source actors
339  return false;
340 }
341 
345 inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck )
346 {
347  bool hittable( true );
348 
349  if(layer.IsClipping())
350  {
351  ClippingBox box = layer.GetClippingBox();
352 
353  if( screenCoordinates.x < box.x ||
354  screenCoordinates.x > box.x + box.width ||
355  screenCoordinates.y < stageSize.y - (box.y + box.height) ||
356  screenCoordinates.y > stageSize.y - box.y)
357  {
358  // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
359  hittable = false;
360  }
361  }
362 
363  if(hittable)
364  {
365  Actor* actor( &layer );
366 
367  // Ensure that we can descend into the layer's (or any of its parent's) hierarchy.
368  while ( actor && hittable )
369  {
370  if ( ! hitCheck.DescendActorHierarchy( actor ) )
371  {
372  hittable = false;
373  break;
374  }
375  actor = actor->GetParent();
376  }
377  }
378 
379  return hittable;
380 }
381 
385 void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane )
386 {
387  CameraActor* cameraActor = renderTask.GetCameraActor();
388  nearClippingPlane = cameraActor->GetNearClippingPlane();
389  farClippingPlane = cameraActor->GetFarClippingPlane();
390 }
391 
396  Stage& stage,
397  LayerList& layers,
398  RenderTask& renderTask,
399  Vector2 screenCoordinates,
400  Results& results,
401  HitTestInterface& hitCheck )
402 {
403  if ( renderTask.IsHittable( screenCoordinates ) )
404  {
405  Viewport viewport;
406  renderTask.GetViewport( viewport );
407  if( screenCoordinates.x < viewport.x ||
408  screenCoordinates.x > viewport.x + viewport.width ||
409  screenCoordinates.y < viewport.y ||
410  screenCoordinates.y > viewport.y + viewport.height )
411  {
412  // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
413  return false;
414  }
415 
416  float nearClippingPlane, farClippingPlane;
417  GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane);
418 
419  // Determine the layer depth of the source actor
420  Actor* sourceActor( renderTask.GetSourceActor() );
421  if ( sourceActor )
422  {
423  Dali::Layer layer( sourceActor->GetLayer() );
424  if ( layer )
425  {
426  const unsigned int sourceActorDepth( layer.GetDepth() );
427 
428  CameraActor* cameraActor = renderTask.GetCameraActor();
429  bool pickingPossible = cameraActor->BuildPickingRay(
430  screenCoordinates,
431  viewport,
432  results.rayOrigin,
433  results.rayDirection );
434  if( !pickingPossible )
435  {
436  return false;
437  }
438 
439  // Hit test starting with the top layer, working towards the bottom layer.
440  HitActor hit;
441  bool stencilOnLayer = false;
442  bool stencilHit = false;
443  bool overlayHit = false;
444  bool layerConsumesHit = false;
445 
446  const Vector2& stageSize = stage.GetSize();
447 
448  for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i)
449  {
450  Layer* layer( layers.GetLayer(i) );
451  HitActor previousHit = hit;
452  stencilOnLayer = false;
453  stencilHit = false;
454  overlayHit = false;
455 
456  // Ensure layer is touchable (also checks whether ancestors are also touchable)
457  if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, hitCheck ) )
458  {
459  // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
460  if ( sourceActorDepth == static_cast<unsigned int>(i) )
461  {
462  // Recursively hit test the source actor & children, without crossing into other layers.
463  hit = HitTestWithinLayer( *sourceActor,
464  renderTask,
465  exclusives,
466  results.rayOrigin,
467  results.rayDirection,
468  nearClippingPlane,
469  farClippingPlane,
470  hitCheck,
471  stencilOnLayer,
472  stencilHit,
473  overlayHit,
474  false,
475  layer->GetBehavior() == Dali::Layer::LAYER_3D);
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  stencilOnLayer,
489  stencilHit,
490  overlayHit,
491  false,
492  layer->GetBehavior() == Dali::Layer::LAYER_3D);
493  }
494 
495  // If a stencil on this layer hasn't been hit, then discard hit results for this layer if our current hit actor is renderable
496  if ( stencilOnLayer && !stencilHit &&
497  hit.actor && hit.actor->IsRenderable() )
498  {
499  hit = previousHit;
500  }
501 
502  // If this layer is set to consume the hit, then do not check any layers behind it
503  if ( hitCheck.DoesLayerConsumeHit( layer ) )
504  {
505  layerConsumesHit = true;
506  break;
507  }
508  }
509  }
510  if ( hit.actor )
511  {
512  results.renderTask = Dali::RenderTask(&renderTask);
513  results.actor = Dali::Actor(hit.actor);
514  results.actorCoordinates.x = hit.x;
515  results.actorCoordinates.y = hit.y;
516  return true; // Success
517  }
518  else if ( layerConsumesHit )
519  {
520  return true; // Also success if layer is consuming the hit
521  }
522  }
523  }
524  }
525  return false;
526 }
527 
534  LayerList& layers,
535  RenderTaskList& taskList,
536  const Vector2& screenCoordinates,
537  Results& results,
538  HitTestInterface& hitCheck )
539 {
540  RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
541  RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
542 
543  const Vector< RenderTaskList::Exclusive >& exclusives = taskList.GetExclusivesList();
544 
545  // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first).
546 
547  // on screen
548  for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
549  {
550  RenderTask& renderTask = GetImplementation( *iter );
551  Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
552 
553  // Note that if frameBufferImage is NULL we are using the default (on screen) render target
554  if(frameBufferImage)
555  {
556  ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
557 
558  // on screen only
559  if(0 != id)
560  {
561  // Skip to next task
562  continue;
563  }
564  }
565 
566  if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
567  {
568  // Return true when an actor is hit (or layer in our render-task consumes the hit)
569  return true; // don't bother checking off screen tasks
570  }
571  }
572 
573  // off screen
574  for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
575  {
576  RenderTask& renderTask = GetImplementation( *iter );
577  Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
578 
579  // Note that if frameBufferImage is NULL we are using the default (on screen) render target
580  if(frameBufferImage)
581  {
582  ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
583 
584  // off screen only
585  if(0 == id)
586  {
587  // Skip to next task
588  continue;
589  }
590 
591  if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
592  {
593  // Return true when an actor is hit (or a layer in our render-task consumes the hit)
594  return true;
595  }
596  }
597  }
598  return false;
599 }
600 
601 } // unnamed namespace
602 
603 bool HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
604 {
605  bool wasHit( false );
606  // Hit-test the regular on-stage actors
607  RenderTaskList& taskList = stage.GetRenderTaskList();
608  LayerList& layerList = stage.GetLayerList();
609 
610  Results hitTestResults;
611  HitTestFunctionWrapper hitTestFunctionWrapper( func );
612  if ( HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
613  {
614  results.actor = hitTestResults.actor;
615  results.actorCoordinates = hitTestResults.actorCoordinates;
616  wasHit = true;
617  }
618  return wasHit;
619 }
620 
621 bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface )
622 {
623  bool wasHit( false );
624 
625  // Hit-test the system-overlay actors first
626  SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal();
627 
628  if ( systemOverlay )
629  {
630  RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks();
631  LayerList& overlayLayerList = systemOverlay->GetLayerList();
632 
633  wasHit = HitTestForEachRenderTask( stage, overlayLayerList, overlayTaskList, screenCoordinates, results, hitTestInterface );
634  }
635 
636  // Hit-test the regular on-stage actors
637  if ( !wasHit )
638  {
639  RenderTaskList& taskList = stage.GetRenderTaskList();
640  LayerList& layerList = stage.GetLayerList();
641 
642  wasHit = HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, results, hitTestInterface );
643  }
644  return wasHit;
645 }
646 
647 bool HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results )
648 {
649  ActorTouchableCheck actorTouchableCheck;
650  return HitTest( stage, screenCoordinates, results, actorTouchableCheck );
651 }
652 
653 bool HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates,
655 {
656  bool wasHit( false );
657  Results hitTestResults;
658 
660  HitTestFunctionWrapper hitTestFunctionWrapper( func );
661  if ( HitTestRenderTask( exclusives, stage, stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
662  {
663  results.actor = hitTestResults.actor;
664  results.actorCoordinates = hitTestResults.actorCoordinates;
665  wasHit = true;
666  }
667  return wasHit;
668 }
669 
670 } // namespace HitTestAlgorithm
671 
672 } // namespace Internal
673 
674 } // namespace Dali
Dali Docs Home
Read more about Dali