2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
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
8 // http://floralicense.org/license/
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.
18 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
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/images/frame-buffer-image-impl.h>
34 #include <dali/internal/event/render-tasks/render-task-impl.h>
35 #include <dali/internal/event/render-tasks/render-task-list-impl.h>
43 namespace HitTestAlgorithm
55 distance( std::numeric_limits<float>::max() ),
61 Actor *actor; ///< the actor hit. (if actor hit, then initialised)
62 float x; ///< x position of hit (only valid if actor valid)
63 float y; ///< y position of hit (only valid if actor valid)
64 float distance; ///< distance from ray origin to hit actor
65 bool overlay; ///< true if the hit actor is an overlay
70 * The function to be used in the hit-test algorithm to check whether the actor is touchable.
71 * It is used by the touch event and gesture processor.
73 bool IsActorTouchableFunction(Dali::Actor actor, Dali::HitTestAlgorithm::TraverseType type)
75 bool hittable = false;
79 case Dali::HitTestAlgorithm::CHECK_ACTOR:
81 if( GetImplementation(actor).GetTouchRequired() && // Does the Application or derived actor type require a touch event?
82 GetImplementation(actor).IsHittable() ) // Is actor sensitive, visible and on the scene?
88 case Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE:
90 if( actor.IsVisible() && // Actor is visible, if not visible then none of its children are visible.
91 actor.IsSensitive() ) // Actor is sensitive, if insensitive none of its children should be hittable either.
107 * Recursively hit test all the actors, without crossing into other layers.
108 * This algorithm performs a Depth-First-Search (DFS) on all Actors within Layer.
109 * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin
110 * of touch vector). The closest Hit-Tested Actor is that which is returned.
111 * Exceptions to this rule are:
112 * - If the Actor is an overlay then it is considered closer than all previous
113 * overlays encountered in the hit test traversal.
114 * - When comparing against renderable parents, if Actor is the same distance
115 * or closer than it's renderable parent, then it takes priority.
117 HitActor HitTestWithinLayer( Actor& actor,
118 const Vector4& rayOrigin,
119 const Vector4& rayDir,
121 float& nearClippingPlane,
122 float& farClippingPlane,
123 Dali::HitTestAlgorithm::HitTestFunction func,
124 bool& stencilOnLayer,
126 bool parentIsStencil )
128 worldOverlay |= actor.IsOverlay();
132 // Children should inherit the stencil draw mode
133 bool isStencil = parentIsStencil;
135 if ( actor.GetDrawMode() == DrawMode::STENCIL && actor.IsVisible() )
138 stencilOnLayer = true;
141 // If we are a stencil or hittable...
142 if ( isStencil || func(Dali::Actor(&actor), Dali::HitTestAlgorithm::CHECK_ACTOR) ) // Is actor hittable
144 Vector3 size( actor.GetCurrentSize() );
146 if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size.
147 actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor.
149 Vector4 hitPointLocal;
152 // Finally, perform a more accurate ray test to see if our ray actually hits the actor.
153 if( actor.RayActorTest( rayOrigin, rayDir, hitPointLocal, distance ) )
155 if( distance >= nearClippingPlane && distance <= farClippingPlane )
157 // If the hit has happened on a stencil then register, but don't record as hit result
165 hit.x = hitPointLocal.x;
166 hit.y = hitPointLocal.y;
167 hit.distance = distance;
168 hit.overlay = worldOverlay;
175 // If there is a stencil on this layer and we've also registered a hit, then don't both searching any children
176 if ( stencilHit && hit.actor )
181 // Find a child hit, until we run out of actors in the current layer.
183 if( actor.GetChildCount() > 0 )
185 childHit.distance = std::numeric_limits<float>::max();
186 Dali::ActorContainer& children = actor.GetChildrenInternal();
188 // Hit test ALL children and calculate their distance.
189 bool parentIsRenderable = actor.IsRenderable();
191 for (Dali::ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter)
193 // Descend tree only if...
194 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
195 ( isStencil || func(*iter, Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE ) ) ) // Child is visible and sensitive, otherwise none of its children should be hittable.
197 HitActor currentHit( HitTestWithinLayer( GetImplementation(*iter), rayOrigin, rayDir, worldOverlay, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, isStencil ) );
199 // If Current child is an overlay, then it takes priority.
200 // If it is not an overlay, and the previously hit sibling is also not an overlay, then closest takes priority.
201 // (last overlay sibling has priority as is rendered on top)
202 if ( currentHit.distance >= 0.f && (currentHit.overlay || (!childHit.overlay && currentHit.distance < childHit.distance) ) )
204 if ( !parentIsRenderable )
206 // If our parent is not renderable, then child should be hit regardless of distance.
207 childHit = currentHit;
209 else if ( currentHit.overlay || (!hit.overlay && currentHit.distance <= hit.distance) )
211 // 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.
212 // (child overlay has priority as is rendered on top of it's parent)
213 childHit = currentHit;
219 return ( childHit.actor ) ? childHit : hit;
223 * Return true if actor is sourceActor or a descendent of sourceActor
225 bool IsWithinSourceActors( const Actor& sourceActor, const Actor& actor )
227 if ( &sourceActor == &actor )
233 Actor* parent = actor.GetParent();
236 return IsWithinSourceActors( sourceActor, *parent );
240 // Not within source actors
245 * Returns true if the layer and all of the layer's parents are visible and sensitive.
247 inline bool IsActuallyHittable( Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, Dali::HitTestAlgorithm::HitTestFunction func )
249 bool hittable( true );
251 if(layer.IsClipping())
253 ClippingBox box = layer.GetClippingBox();
255 if( screenCoordinates.x < box.x ||
256 screenCoordinates.x > box.x + box.width ||
257 screenCoordinates.y < stageSize.y - (box.y + box.height) ||
258 screenCoordinates.y > stageSize.y - box.y)
260 // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
267 Actor* actor( &layer );
268 while ( actor && hittable )
270 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.
275 actor = actor->GetParent();
283 * Gets the near and far clipping planes of the camera from which the scene is viewed in the render task.
285 void GetCameraClippingPlane( RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane )
287 CameraActor* cameraActor = renderTask.GetCameraActor();
288 nearClippingPlane = cameraActor->GetNearClippingPlane();
289 farClippingPlane = cameraActor->GetFarClippingPlane();
293 * Hit test a RenderTask
295 bool HitTestRenderTask( LayerList& layers,
296 RenderTask& renderTask,
297 Vector2 screenCoordinates,
299 Dali::HitTestAlgorithm::HitTestFunction func )
301 if ( renderTask.IsHittable( screenCoordinates ) )
304 renderTask.GetViewport( viewport );
305 if( screenCoordinates.x < viewport.x ||
306 screenCoordinates.x > viewport.x + viewport.width ||
307 screenCoordinates.y < viewport.y ||
308 screenCoordinates.y > viewport.y + viewport.height )
310 // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
314 float nearClippingPlane, farClippingPlane;
315 GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane);
317 // Determine the layer depth of the source actor
318 Actor* sourceActor( renderTask.GetSourceActor() );
321 Dali::Layer layer( sourceActor->GetLayer() );
324 const unsigned int sourceActorDepth( layer.GetDepth() );
326 CameraActor* cameraActor = renderTask.GetCameraActor();
327 bool pickingPossible = cameraActor->BuildPickingRay(
331 results.rayDirection );
332 if( !pickingPossible )
337 // Hit test starting with the top layer, working towards the bottom layer.
339 bool stencilOnLayer = false;
340 bool stencilHit = false;
341 const Vector2& stageSize = Stage::GetCurrent()->GetSize();
343 for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i)
345 Layer* layer( layers.GetLayer(i) );
347 HitActor previousHit = hit;
348 stencilOnLayer = false;
351 // Ensure layer is touchable (also checks whether ancestors are also touchable)
352 if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, func ) )
354 // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
355 if ( sourceActorDepth == static_cast<unsigned int>(i) )
357 // Recursively hit test the source actor & children, without crossing into other layers.
358 hit = HitTestWithinLayer( *sourceActor, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false );
360 else if ( IsWithinSourceActors( *sourceActor, *layer ) )
362 // Recursively hit test all the actors, without crossing into other layers.
363 hit = HitTestWithinLayer( *layer, results.rayOrigin, results.rayDirection, false, nearClippingPlane, farClippingPlane, func, stencilOnLayer, stencilHit, false );
365 // If a stencil on this layer hasn't been hit, then discard hit results for this layer
366 if ( stencilOnLayer && !stencilHit )
374 results.renderTask = Dali::RenderTask(&renderTask);
375 results.actor = Dali::Actor(hit.actor);
376 results.actorCoordinates.x = hit.x;
377 results.actorCoordinates.y = hit.y;
378 return true; // Success
387 * Iterate through RenderTaskList and perform hit test.
390 void HitTestForEachRenderTask( LayerList& layers,
391 RenderTaskList& taskList,
392 const Vector2& screenCoordinates,
394 Dali::HitTestAlgorithm::HitTestFunction func )
396 RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
397 RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
399 // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first).
402 for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
404 RenderTask& renderTask = GetImplementation( *iter );
405 Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
407 // Note that if frameBufferImage is NULL we are using the default (on screen) render target
410 ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
420 if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) )
422 // Exit when an actor is hit
423 return; // don't bother checking off screen tasks
428 for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
430 RenderTask& renderTask = GetImplementation( *iter );
431 Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
433 // Note that if frameBufferImage is NULL we are using the default (on screen) render target
436 ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
445 if ( HitTestRenderTask( layers, renderTask, screenCoordinates, results, func ) )
447 // Exit when an actor is hit
454 } // unnamed namespace
456 void HitTest( Stage& stage, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
458 // Hit-test the regular on-stage actors
459 RenderTaskList& taskList = stage.GetRenderTaskList();
460 LayerList& layerList = stage.GetLayerList();
462 Results hitTestResults;
463 HitTestForEachRenderTask( layerList, taskList, screenCoordinates, hitTestResults, func );
465 results.actor = hitTestResults.actor;
466 results.actorCoordinates = hitTestResults.actorCoordinates;
469 void HitTest( Stage& stage, const Vector2& screenCoordinates, Results& results )
471 // Hit-test the system-overlay actors first
472 SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal();
476 RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks();
477 LayerList& overlayLayerList = systemOverlay->GetLayerList();
479 HitTestForEachRenderTask( overlayLayerList, overlayTaskList, screenCoordinates, results, IsActorTouchableFunction );
482 // Hit-test the regular on-stage actors
483 if ( !results.actor )
485 RenderTaskList& taskList = stage.GetRenderTaskList();
486 LayerList& layerList = stage.GetLayerList();
488 HitTestForEachRenderTask( layerList, taskList, screenCoordinates, results, IsActorTouchableFunction );
492 void HitTest( Stage& stage, RenderTask& renderTask, const Vector2& screenCoordinates,
493 Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func )
495 Results hitTestResults;
496 HitTestRenderTask( stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, func );
497 results.actor = hitTestResults.actor;
498 results.actorCoordinates = hitTestResults.actorCoordinates;
501 } // namespace HitTestAlgorithm
503 } // namespace Internal