2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali/internal/event/events/hit-test-algorithm-impl.h>
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/public-api/actors/layer.h>
32 #include <dali/public-api/math/vector2.h>
33 #include <dali/public-api/math/vector4.h>
39 namespace HitTestAlgorithm
47 distance(std::numeric_limits<float>::max()),
48 depth(std::numeric_limits<int>::min())
52 Actor* actor; ///< The actor hit (if actor is hit, then this is initialised).
53 Vector2 hitPosition; ///< Position of hit (only valid if actor valid).
54 float distance; ///< Distance from ray origin to hit actor.
55 int32_t depth; ///< Depth index of this actor.
59 * Creates an Actor handle so that a HitTestFunction provided via the public API can be called.
61 struct HitTestFunctionWrapper : public HitTestInterface
66 * @param[in] func HitTestFunction to call with an Actor handle.
68 HitTestFunctionWrapper(Dali::HitTestAlgorithm::HitTestFunction func)
73 bool IsActorHittable(Actor* actor) override
75 return mFunc(Dali::Actor(actor), Dali::HitTestAlgorithm::CHECK_ACTOR);
78 bool DescendActorHierarchy(Actor* actor) override
80 return mFunc(Dali::Actor(actor), Dali::HitTestAlgorithm::DESCEND_ACTOR_TREE);
83 bool DoesLayerConsumeHit(Layer* layer) override
85 // Layer::IsTouchConsumed() focuses on touch only. Here we are a wrapper for the public-api
86 // where the caller may want to check for something completely different.
87 // TODO: Should provide a means to let caller decide. For now do not allow layers to consume
91 Dali::HitTestAlgorithm::HitTestFunction mFunc;
95 * Used in the hit-test algorithm to check whether the actor is touchable.
96 * It is used by the touch event processor.
98 struct ActorTouchableCheck : public HitTestInterface
100 bool IsActorHittable(Actor* actor) override
102 return (actor->GetTouchRequired() || actor->IsTouchFocusable()) && // Does the Application or derived actor type require a touch event? or focusable by touch?
103 actor->IsHittable(); // Is actor sensitive, visible and on the scene?
106 bool DescendActorHierarchy(Actor* actor) override
108 return actor->IsVisible() && // Actor is visible, if not visible then none of its children are visible.
109 actor->IsSensitive(); // Actor is sensitive, if insensitive none of its children should be hittable either.
112 bool DoesLayerConsumeHit(Layer* layer) override
114 return layer->IsTouchConsumed();
119 * Check to see if the actor we're about to hit test is exclusively owned by another rendertask?
121 bool IsActorExclusiveToAnotherRenderTask(const Actor& actor,
122 const RenderTask& renderTask,
123 const RenderTaskList::ExclusivesContainer& exclusives)
126 if(exclusives.size())
128 for(const auto& exclusive : exclusives)
130 if((exclusive.renderTaskPtr != &renderTask) && (exclusive.actor.GetActor() == &actor))
140 * Recursively hit test all the actors, without crossing into other layers.
141 * This algorithm performs a Depth-First-Search (DFS) on all Actors within Layer.
142 * Hit-Testing each Actor, noting the distance from the Ray-Origin (3D origin
143 * of touch vector). The closest Hit-Tested Actor is that which is returned.
144 * Exceptions to this rule are:
145 * - When comparing against renderable parents, if Actor is the same distance
146 * or closer than it's renderable parent, then it takes priority.
148 HitActor HitTestWithinLayer(Actor& actor,
149 const RenderTask& renderTask,
150 const RenderTaskList::ExclusivesContainer& exclusives,
151 const Vector4& rayOrigin,
152 const Vector4& rayDir,
153 float& nearClippingPlane,
154 float& farClippingPlane,
155 HitTestInterface& hitCheck,
158 uint32_t clippingDepth,
159 uint32_t clippingBitPlaneMask,
160 const RayTest& rayTest)
164 if(IsActorExclusiveToAnotherRenderTask(actor, renderTask, exclusives))
169 // For clipping, regardless of whether we have hit this actor or not,
170 // we increase the clipping depth if we have hit a clipping actor.
171 // This is used later to ensure all nested clipped children have hit
172 // all clipping actors also for them to be counted as hit.
173 uint32_t newClippingDepth = clippingDepth;
174 bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED;
180 // If we are a clipping actor or hittable...
181 if(clippingActor || hitCheck.IsActorHittable(&actor))
183 Vector3 size(actor.GetCurrentSize());
185 // Ensure the actor has a valid size.
186 // If so, perform a quick ray sphere test to see if our ray is close to the actor.
187 if(size.x > 0.0f && size.y > 0.0f && rayTest.SphereTest(actor, rayOrigin, rayDir))
189 Vector2 hitPointLocal;
192 // Finally, perform a more accurate ray test to see if our ray actually hits the actor.
193 if(rayTest.ActorTest(actor, rayOrigin, rayDir, hitPointLocal, distance))
195 if(distance >= nearClippingPlane && distance <= farClippingPlane)
197 // If the hit has happened on a clipping actor, then add this clipping depth to the mask of hit clipping depths.
198 // This mask shows all the actors that have been hit at different clipping depths.
201 clippingBitPlaneMask |= 1u << clippingDepth;
204 if(overlayHit && !actor.IsOverlay())
206 // If we have already hit an overlay and current actor is not an overlay ignore current actor.
210 if(actor.IsOverlay())
215 // At this point we have hit an actor.
216 // Now perform checks for clipping.
217 // Assume we have hit the actor first as if it is not clipped this would be the case.
218 bool haveHitActor = true;
220 // Check if we are performing clipping. IE. if any actors so far have clipping enabled - not necessarily this one.
221 // We can do this by checking the clipping depth has a value 1 or above.
222 if(newClippingDepth >= 1u)
224 // Now for us to count this actor as hit, we must have also hit
225 // all CLIPPING actors up to this point in the hierarchy as well.
226 // This information is stored in the clippingBitPlaneMask we updated above.
227 // Here we calculate a comparison mask by setting all the bits up to the current depth value.
228 // EG. a depth of 4 (10000 binary) = a mask of 1111 binary.
229 // This allows us a fast way of comparing all bits are set up to this depth.
230 // Note: If the current Actor has clipping, that is included in the depth mask too.
231 uint32_t clippingDepthMask = (1u << newClippingDepth) - 1u;
233 // The two masks must be equal to be a hit, as we are already assuming a hit
234 // (for non-clipping mode) then they must be not-equal to disqualify the hit.
235 if(clippingBitPlaneMask != clippingDepthMask)
237 haveHitActor = false;
244 hit.hitPosition = hitPointLocal;
245 hit.distance = distance;
246 hit.depth = actor.GetSortingDepth();
248 if(actor.GetRendererCount() > 0)
250 //Get renderer with maximum depth
251 int rendererMaxDepth(actor.GetRendererAt(0).Get()->GetDepthIndex());
252 for(uint32_t i(1); i < actor.GetRendererCount(); ++i)
254 int depth = actor.GetRendererAt(i).Get()->GetDepthIndex();
255 if(depth > rendererMaxDepth)
257 rendererMaxDepth = depth;
260 hit.depth += rendererMaxDepth;
269 // Find a child hit, until we run out of actors in the current layer.
271 if(actor.GetChildCount() > 0)
273 childHit.distance = std::numeric_limits<float>::max();
274 childHit.depth = std::numeric_limits<int32_t>::min();
275 ActorContainer& children = actor.GetChildrenInternal();
277 // Hit test ALL children and calculate their distance.
278 bool parentIsRenderable = actor.IsRenderable();
280 for(ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter)
282 // Descend tree only if...
283 if(!(*iter)->IsLayer() && // Child is NOT a layer, hit testing current layer only
284 (hitCheck.DescendActorHierarchy((*iter).Get()))) // We can descend into child hierarchy
286 HitActor currentHit(HitTestWithinLayer((*iter->Get()),
297 clippingBitPlaneMask,
300 // Make sure the set hit actor is actually hittable. This is usually required when we have some
301 // clipping as we need to hit-test all actors as we descend the tree regardless of whether they
302 // are hittable or not.
303 if(currentHit.actor && !hitCheck.IsActorHittable(currentHit.actor))
308 bool updateChildHit = false;
309 if(currentHit.distance >= 0.0f)
313 updateChildHit = ((currentHit.depth > childHit.depth) ||
314 ((currentHit.depth == childHit.depth) && (currentHit.distance < childHit.distance)));
318 updateChildHit = currentHit.depth >= childHit.depth;
324 if(!parentIsRenderable || currentHit.depth > hit.depth ||
325 (layerIs3d && (currentHit.depth == hit.depth && currentHit.distance < hit.distance)))
327 childHit = currentHit;
334 return (childHit.actor) ? childHit : hit;
338 * Return true if actor is sourceActor or a descendent of sourceActor
340 bool IsWithinSourceActors(const Actor& sourceActor, const Actor& actor)
342 if(&sourceActor == &actor)
347 Actor* parent = actor.GetParent();
350 return IsWithinSourceActors(sourceActor, *parent);
353 // Not within source actors
358 * Returns true if the layer and all of the layer's parents are visible and sensitive.
360 inline bool IsActuallyHittable(Layer& layer, const Vector2& screenCoordinates, const Vector2& stageSize, HitTestInterface& hitCheck)
364 if(layer.IsClipping())
366 ClippingBox box = layer.GetClippingBox();
368 if(screenCoordinates.x < static_cast<float>(box.x) ||
369 screenCoordinates.x > static_cast<float>(box.x + box.width) ||
370 screenCoordinates.y < stageSize.y - static_cast<float>(box.y + box.height) ||
371 screenCoordinates.y > stageSize.y - static_cast<float>(box.y))
373 // Not touchable if clipping is enabled in the layer and the screen coordinate is outside the clip region.
380 Actor* actor(&layer);
382 // Ensure that we can descend into the layer's (or any of its parent's) hierarchy.
383 while(actor && hittable)
385 if(!hitCheck.DescendActorHierarchy(actor))
390 actor = actor->GetParent();
398 * Gets the near and far clipping planes of the camera from which the scene is viewed in the render task.
400 void GetCameraClippingPlane(RenderTask& renderTask, float& nearClippingPlane, float& farClippingPlane)
402 CameraActor* cameraActor = renderTask.GetCameraActor();
403 nearClippingPlane = cameraActor->GetNearClippingPlane();
404 farClippingPlane = cameraActor->GetFarClippingPlane();
408 * Hit test a RenderTask
410 bool HitTestRenderTask(const RenderTaskList::ExclusivesContainer& exclusives,
411 const Vector2& sceneSize,
413 RenderTask& renderTask,
414 Vector2 screenCoordinates,
416 HitTestInterface& hitCheck,
417 const RayTest& rayTest)
419 if(renderTask.IsHittable(screenCoordinates))
422 renderTask.GetViewport(viewport);
423 if(screenCoordinates.x < static_cast<float>(viewport.x) ||
424 screenCoordinates.x > static_cast<float>(viewport.x + viewport.width) ||
425 screenCoordinates.y < static_cast<float>(viewport.y) ||
426 screenCoordinates.y > static_cast<float>(viewport.y + viewport.height))
428 // The screen coordinate is outside the viewport of render task. The viewport clips all layers.
432 float nearClippingPlane, farClippingPlane;
433 GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane);
435 // Determine the layer depth of the source actor
436 Actor* sourceActor(renderTask.GetSourceActor());
439 Dali::Layer layer(sourceActor->GetLayer());
442 const uint32_t sourceActorDepth(layer.GetProperty<bool>(Dali::Layer::Property::DEPTH));
444 CameraActor* cameraActor = renderTask.GetCameraActor();
445 bool pickingPossible = cameraActor->BuildPickingRay(
449 results.rayDirection);
455 // Hit test starting with the top layer, working towards the bottom layer.
457 bool overlayHit = false;
458 bool layerConsumesHit = false;
460 for(int32_t i = layers.GetLayerCount() - 1; i >= 0 && !(hit.actor); --i)
462 Layer* layer(layers.GetLayer(i));
465 // Ensure layer is touchable (also checks whether ancestors are also touchable)
466 if(IsActuallyHittable(*layer, screenCoordinates, sceneSize, hitCheck))
468 // Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
469 if(sourceActorDepth == static_cast<uint32_t>(i))
471 // Recursively hit test the source actor & children, without crossing into other layers.
472 hit = HitTestWithinLayer(*sourceActor,
476 results.rayDirection,
481 layer->GetBehavior() == Dali::Layer::LAYER_3D,
486 else if(IsWithinSourceActors(*sourceActor, *layer))
488 // Recursively hit test all the actors, without crossing into other layers.
489 hit = HitTestWithinLayer(*layer,
493 results.rayDirection,
498 layer->GetBehavior() == Dali::Layer::LAYER_3D,
504 // If this layer is set to consume the hit, then do not check any layers behind it
505 if(hitCheck.DoesLayerConsumeHit(layer))
507 layerConsumesHit = true;
515 results.renderTask = RenderTaskPtr(&renderTask);
516 results.actor = Dali::Actor(hit.actor);
517 results.actorCoordinates = hit.hitPosition;
519 return true; // Success
524 return true; // Also success if layer is consuming the hit
533 * Iterate through the RenderTaskList and perform hit testing.
535 * @param[in] sceneSize The scene size the tests will be performed in
536 * @param[in] layers The list of layers to test
537 * @param[in] taskList The list of render tasks
538 * @param[out] results Ray information calculated by the camera
539 * @param[in] hitCheck The hit testing interface object to use
540 * @param[in] onScreen True to test on-screen, false to test off-screen
541 * @return True if we have a hit, false otherwise
543 bool HitTestRenderTaskList(const Vector2& sceneSize,
545 RenderTaskList& taskList,
546 const Vector2& screenCoordinates,
548 HitTestInterface& hitCheck,
551 RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
552 RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
553 const auto& exclusives = taskList.GetExclusivesList();
556 for(RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter)
558 RenderTask& renderTask = *iter->Get();
559 const bool isOffscreenRenderTask = renderTask.GetFrameBuffer();
560 if((onScreen && isOffscreenRenderTask) || (!onScreen && !isOffscreenRenderTask))
566 if(HitTestRenderTask(exclusives, sceneSize, layers, renderTask, screenCoordinates, results, hitCheck, rayTest))
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
577 * Iterate through the RenderTaskList and perform hit testing for both on-screen and off-screen.
579 * @param[in] sceneSize The scene size the tests will be performed in
580 * @param[in] layers The list of layers to test
581 * @param[in] taskList The list of render tasks
582 * @param[out] results Ray information calculated by the camera
583 * @param[in] hitCheck The hit testing interface object to use
584 * @param[in] onScreen True to test on-screen, false to test off-screen
585 * @return True if we have a hit, false otherwise
587 bool HitTestForEachRenderTask(const Vector2& sceneSize,
589 RenderTaskList& taskList,
590 const Vector2& screenCoordinates,
592 HitTestInterface& hitCheck)
596 // Check on-screen tasks before off-screen ones.
597 // Hit test order should be reverse of draw order (see ProcessRenderTasks() where off-screen tasks are drawn first).
598 if(HitTestRenderTaskList(sceneSize, layers, taskList, screenCoordinates, results, hitCheck, true) ||
599 HitTestRenderTaskList(sceneSize, layers, taskList, screenCoordinates, results, hitCheck, false))
608 } // unnamed namespace
610 HitTestInterface::~HitTestInterface() = default;
612 bool HitTest(const Vector2& sceneSize, RenderTaskList& taskList, LayerList& layerList, const Vector2& screenCoordinates, Dali::HitTestAlgorithm::Results& results, Dali::HitTestAlgorithm::HitTestFunction func)
615 // Hit-test the regular on-scene actors
616 Results hitTestResults;
617 HitTestFunctionWrapper hitTestFunctionWrapper(func);
618 if(HitTestForEachRenderTask(sceneSize, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper))
620 results.actor = hitTestResults.actor;
621 results.actorCoordinates = hitTestResults.actorCoordinates;
627 bool HitTest(const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitTestInterface)
631 // Hit-test the regular on-scene actors
634 wasHit = HitTestForEachRenderTask(sceneSize, layerList, renderTaskList, screenCoordinates, results, hitTestInterface);
639 bool HitTest(const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList& layerList, const Vector2& screenCoordinates, Results& results)
641 ActorTouchableCheck actorTouchableCheck;
642 return HitTest(sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck);
645 } // namespace HitTestAlgorithm
647 } // namespace Internal