/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
DALI_TEST_EQUALS(built, false, TEST_LOCATION);
END_TEST;
+}
+
+int UtcDaliHitTestAlgorithmOverlayWithClipping(void)
+{
+ TestApplication application;
+ tet_infoline("Testing Dali::HitTestAlgorithm with overlay actors and some different clipping configurations");
+
+ Stage stage = Stage::GetCurrent();
+ Actor rootLayer = stage.GetRootLayer();
+
+ auto createActor = [&](const Vector3& position) {
+ Actor actor = Handle::New<Actor>(
+ {
+ {Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER},
+ {Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER},
+ {Actor::Property::SIZE, Vector3(200.0f, 200.0f, 0.0f)},
+ {Actor::Property::POSITION, position},
+ });
+ return actor;
+ };
+
+ auto hitTest = [&stage](const Vector2& screenCoordinates) {
+ HitTestAlgorithm::Results results;
+ HitTest(stage, screenCoordinates, results, &DefaultIsActorTouchableFunction);
+ return results.actor;
+ };
+
+ auto red = createActor(Vector3(-25.0f, -75.0f, 0.0f));
+ auto green = createActor(Vector3(25.0f, 75.0f, 0.0f));
+ auto blue = createActor(Vector3(100.0f, 100.0f, 0.0f));
+
+ stage.Add(red);
+ stage.Add(green);
+ red.Add(blue);
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Points to test
+ Vector2 point1(275.0f, 405.0f);
+ Vector2 point2(338.0f, 336.0f);
+ Vector2 point3(246.0f, 347.0f);
+ Vector2 point4(189.0f, 397.0f);
+ Vector2 point5(187.0f, 295.0f);
+ Vector2 point6(357.0f, 296.0f);
+
+ /* No Clip, No Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+------+
+ | | 3 2 |
+ | +---+------------+ |
+ | | 4 1 | |
+ +--+ | |
+ | | B |
+ | | L |
+ | | U |
+ | | E |
+ | +---+
+ |GREEN |
+ +----------------+
+ */
+ DALI_TEST_CHECK(hitTest(point1) == green);
+ DALI_TEST_CHECK(hitTest(point2) == blue);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ /* red:CLIP_TO_BOUNDING_BOX, No Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+
+ | | 3 BLUE| 2
+ | +---+---------+--+
+ | | 4 1 |
+ +--+ |
+ | |
+ | |
+ | |
+ | |
+ | |
+ |GREEN |
+ +----------------+
+ */
+ red.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX);
+ application.SendNotification();
+ application.Render();
+ DALI_TEST_CHECK(hitTest(point1) == green);
+ DALI_TEST_CHECK(hitTest(point2) == rootLayer);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ /* red:CLIP_TO_BOUNDING_BOX, blue:Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+------+
+ | | 3 2 |
+ | +---+ |
+ | | 4 | 1 |
+ +--+ | |
+ | | B |
+ | | L |
+ | | U |
+ | | E |
+ | +------------+---+
+ |GREEN |
+ +----------------+
+ */
+ blue.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
+ application.SendNotification();
+ application.Render();
+ DALI_TEST_CHECK(hitTest(point1) == blue);
+ DALI_TEST_CHECK(hitTest(point2) == blue);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ /* No clipping, blue:Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+------+
+ | | 3 2 |
+ | +---+ |
+ | | 4 | 1 |
+ +--+ | |
+ | | B |
+ | | L |
+ | | U |
+ | | E |
+ | +------------+---+
+ |GREEN |
+ +----------------+
+ */
+ red.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::DISABLED);
+ blue.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
+ application.SendNotification();
+ application.Render();
+ DALI_TEST_CHECK(hitTest(point1) == blue);
+ DALI_TEST_CHECK(hitTest(point2) == blue);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ /* red:CLIP_CHILDREN, No Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+
+ | | 3 BLUE| 2
+ | +---+---------+--+
+ | | 4 1 |
+ +--+ |
+ | |
+ | |
+ | |
+ | |
+ | |
+ |GREEN |
+ +----------------+
+ */
+ red.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_CHILDREN);
+ blue.SetProperty(Actor::Property::DRAW_MODE, DrawMode::NORMAL);
+ application.SendNotification();
+ application.Render();
+ DALI_TEST_CHECK(hitTest(point1) == green);
+ DALI_TEST_CHECK(hitTest(point2) == rootLayer);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ /* red:CLIP_CHILDREN, blue:Overlay
+ +----------------+
+ |RED |
+ | |
+ | |
+ | 5 | 6
+ | +---------+
+ | | 3 | 2
+ | +---+ +--+
+ | | 4 | 1 | |
+ +--+ | | |
+ | |BLUE | |
+ | +---------+ |
+ | |
+ | |
+ | |
+ |GREEN |
+ +----------------+
+ */
+ red.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_CHILDREN);
+ blue.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
+ application.SendNotification();
+ application.Render();
+ DALI_TEST_CHECK(hitTest(point1) == blue);
+ DALI_TEST_CHECK(hitTest(point2) == rootLayer);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == green);
+ DALI_TEST_CHECK(hitTest(point5) == red);
+ DALI_TEST_CHECK(hitTest(point6) == rootLayer);
+
+ END_TEST;
+}
+
+int UtcDaliHitTestAlgorithmOverlayWithClippingComplicatedHierarchy(void)
+{
+ TestApplication application;
+ tet_infoline("Testing Dali::HitTestAlgorithm with different overlay actors and clipping configurations throughout a hierarchy");
+
+ Stage stage = Stage::GetCurrent();
+ Actor rootLayer = stage.GetRootLayer();
+
+ auto createActor = [&](const Vector3& position) {
+ Actor actor = Handle::New<Actor>(
+ {
+ {Actor::Property::PARENT_ORIGIN, ParentOrigin::CENTER},
+ {Actor::Property::ANCHOR_POINT, AnchorPoint::CENTER},
+ {Actor::Property::SIZE, Vector3(200.0f, 200.0f, 0.0f)},
+ {Actor::Property::POSITION, position},
+ });
+ return actor;
+ };
+
+ auto hitTest = [&stage](const Vector2& screenCoordinates) {
+ HitTestAlgorithm::Results results;
+ HitTest(stage, screenCoordinates, results, &DefaultIsActorTouchableFunction);
+ return results.actor;
+ };
+
+ auto red = createActor(Vector3(-25.0f, -75.0f, 0.0f));
+ auto green = createActor(Vector3(25.0f, 75.0f, 0.0f));
+ auto blue = createActor(Vector3(100.0f, 100.0f, 0.0f));
+ auto yellow = createActor(Vector3(25.0f, -25.0f, 0.0f));
+ auto purple = createActor(Vector3(25.0f, -25.0f, 0.0f));
+
+ stage.Add(red);
+ stage.Add(green);
+ red.Add(blue);
+ blue.Add(yellow);
+ yellow.Add(purple);
+
+ red.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX);
+ yellow.SetProperty(Actor::Property::CLIPPING_MODE, ClippingMode::CLIP_TO_BOUNDING_BOX);
+ blue.SetProperty(Actor::Property::DRAW_MODE, DrawMode::OVERLAY_2D);
+
+ // Render and notify
+ application.SendNotification();
+ application.Render();
+
+ // Points to test
+ Vector2 point1(195.0f, 404.0f);
+ Vector2 point2(224.0f, 351.0f);
+ Vector2 point3(224.0f, 404.0f);
+ Vector2 point4(254.0f, 309.0f);
+ Vector2 point5(254.0f, 404.0f);
+ Vector2 point6(289.0f, 281.0f);
+ Vector2 point7(289.0f, 309.0f);
+ Vector2 point8(289.0f, 404.0f);
+ Vector2 point9(362.0f, 281.0f);
+ Vector2 point10(362.0f, 309.0f);
+ Vector2 point11(457.0f, 309.0f);
+
+ /*
+ +-----------------+
+ |RED |
+ | |
+ | 6 | 9
+ | +--+---+--------+
+ | |4 | 7 10 | 11
+ | +--+ | |
+ | |2 | | |
+ | +---+ | | |
+ | | 1 |3 |5 | 8 |
+ +---+ | | | |
+ | | | | PURPLE|
+ | | | +------------+
+ | | | YELLOW|
+ | | +------------+--+
+ | | BLUE |
+ | +------------+--+
+ | |
+ | GREEN |
+ +----------------+
+ */
+
+ DALI_TEST_CHECK(hitTest(point1) == green);
+ DALI_TEST_CHECK(hitTest(point2) == blue);
+ DALI_TEST_CHECK(hitTest(point3) == blue);
+ DALI_TEST_CHECK(hitTest(point4) == yellow);
+ DALI_TEST_CHECK(hitTest(point5) == yellow);
+ DALI_TEST_CHECK(hitTest(point6) == red);
+ DALI_TEST_CHECK(hitTest(point7) == purple);
+ DALI_TEST_CHECK(hitTest(point8) == purple);
+ DALI_TEST_CHECK(hitTest(point9) == rootLayer);
+ DALI_TEST_CHECK(hitTest(point10) == purple);
+ DALI_TEST_CHECK(hitTest(point11) == rootLayer);
+
+ END_TEST;
}
\ No newline at end of file
/*
- * Copyright (c) 2023 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2024 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali/public-api/math/vector2.h>
#include <dali/public-api/math/vector4.h>
-namespace Dali
-{
-namespace Internal
-{
-namespace HitTestAlgorithm
+namespace Dali::Internal::HitTestAlgorithm
{
namespace
{
}
/**
+ * Checks if actor or anyone of it's parents are an overlay, until either the currentActor is reached or the root actor
+ * @param actor The child-actor and it's parents to check
+ * @param currentActor The top actor of this current branch which we should not go above
+ * @return True if the actor or a parent is an overlay, false otherwise
+ */
+inline bool IsOnOverlay(Actor* actor, Actor* currentActor)
+{
+ while(actor && actor != currentActor)
+ {
+ if(actor->IsOverlay())
+ {
+ return true;
+ }
+ actor = actor->GetParent();
+ }
+ return false;
+}
+
+/**
* Hit tests the given actor and updates the in/out variables appropriately
*/
void HitTestActor(const RenderTask& renderTask,
if(actor.GetRendererCount() > 0)
{
- //Get renderer with maximum depth
+ // Get renderer with maximum depth
int rendererMaxDepth(actor.GetRendererAt(0).Get()->GetDepthIndex());
for(uint32_t i(1); i < actor.GetRendererCount(); ++i)
{
// For clipping, regardless of whether we have hit this actor or not.
// This is used later to ensure all nested clipped children have hit
// all clipping actors also for them to be counted as hit.
- bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED;
- bool overlayedActor = overlayed || actor.IsOverlay();
+ const ClippingMode::Type clippingMode = actor.GetClippingMode();
+ bool clippingActor = clippingMode != ClippingMode::DISABLED;
+ bool overlayedActor = overlayed || actor.IsOverlay();
// If we are a clipping actor or hittable...
HitTestActor(renderTask, rayOrigin, rayDir, nearClippingPlane, farClippingPlane, hitCheck, rayTest, point, eventTime, clippingActor, overlayedActor, actor, overlayHit, hit, isGeometry);
// If current actor is clipping, and hit failed, We should not checkup child actors. Fast return
- if(clippingActor && !(hit.actor))
+ // Only do this if we're using CLIP_CHILDREN though, as children whose drawing mode is OVERLAY_2D are not clipped when CLIP_TO_BOUNDING_BOX is selected.
+ if(clippingActor && !(hit.actor) && (clippingMode == ClippingMode::CLIP_CHILDREN))
{
return hit;
}
- else if (isGeometry && hit.actor)
+ else if(isGeometry && hit.actor)
{
// Saves the actors that can be hit as a list
actorLists.push_back(hit.actor);
Vector2 hitPointLocal;
float distance;
if(!(rayTest.SphereTest(actor, rayOrigin, rayDir) &&
- rayTest.ActorTest(actor, rayOrigin, rayDir, hitPointLocal, distance)))
+ rayTest.ActorTest(actor, rayOrigin, rayDir, hitPointLocal, distance)))
{
return hit;
}
(hitCheck.DescendActorHierarchy((*iter).Get()))) // We can descend into child hierarchy
{
HitActor currentHit(HitTestWithinLayer((*iter->Get()),
- renderTask,
- exclusives,
- rayOrigin,
- rayDir,
- nearClippingPlane,
- farClippingPlane,
- hitCheck,
- overlayedActor,
- overlayHit,
- layerIs3d,
- rayTest,
- point,
- eventTime,
- actorLists,
- isGeometry));
+ renderTask,
+ exclusives,
+ rayOrigin,
+ rayDir,
+ nearClippingPlane,
+ farClippingPlane,
+ hitCheck,
+ overlayedActor,
+ overlayHit,
+ layerIs3d,
+ rayTest,
+ point,
+ eventTime,
+ actorLists,
+ isGeometry));
// Make sure the set hit actor is actually hittable. This is usually required when we have some
// clipping as we need to hit-test all actors as we descend the tree regardless of whether they
// are hittable or not.
}
}
- return (childHit.actor) ? childHit : hit;
+ if(childHit.actor)
+ {
+ // If child has been hit & current actor is clipping to bounding box...
+ if(clippingMode == ClippingMode::CLIP_TO_BOUNDING_BOX)
+ {
+ // ...then make sure the clipping actor has actually been hit unless the child hit actor is on a child overlay.
+ if(hit.actor || IsOnOverlay(childHit.actor, &actor))
+ {
+ // Only then should we return the child hit in this scenario.
+ return childHit;
+ }
+ }
+ else
+ {
+ // no clipping concerns, return child hit.
+ return childHit;
+ }
+ }
+
+ return hit;
}
/**
}
void GeoHitTestRenderTask(const RenderTaskList::ExclusivesContainer& exclusives,
- const Vector2& sceneSize,
- LayerList& layers,
- RenderTask& renderTask,
- Vector2 screenCoordinates,
- Results& results,
- HitTestInterface& hitCheck,
- const RayTest& rayTest)
+ const Vector2& sceneSize,
+ LayerList& layers,
+ RenderTask& renderTask,
+ Vector2 screenCoordinates,
+ Results& results,
+ HitTestInterface& hitCheck,
+ const RayTest& rayTest)
{
if(renderTask.IsHittable(screenCoordinates))
{
if(sourceLayer)
{
const uint32_t sourceActorDepth(sourceLayer.GetProperty<bool>(Dali::Layer::Property::DEPTH));
- CameraActor* cameraActor = renderTask.GetCameraActor();
- bool pickingPossible = cameraActor->BuildPickingRay(screenCoordinates,
- viewport,
- results.rayOrigin,
- results.rayDirection);
+ CameraActor* cameraActor = renderTask.GetCameraActor();
+ bool pickingPossible = cameraActor->BuildPickingRay(screenCoordinates,
+ viewport,
+ results.rayOrigin,
+ results.rayDirection);
if(!pickingPossible)
{
return;
}
// Hit test starting with the top layer, working towards the bottom layer.
- bool overlayHit = false;
+ bool overlayHit = false;
for(uint32_t i = 0; i < layers.GetLayerCount(); ++i)
{
{
// Recursively hit test the source actor & children, without crossing into other layers.
hit = HitTestWithinLayer(*sourceActor,
- renderTask,
- exclusives,
- results.rayOrigin,
- results.rayDirection,
- nearClippingPlane,
- farClippingPlane,
- hitCheck,
- overlayHit,
- overlayHit,
- layer->GetBehavior() == Dali::Layer::LAYER_3D,
- rayTest,
- results.point,
- results.eventTime,
- results.actorLists,
- true);
+ renderTask,
+ exclusives,
+ results.rayOrigin,
+ results.rayDirection,
+ nearClippingPlane,
+ farClippingPlane,
+ hitCheck,
+ overlayHit,
+ overlayHit,
+ layer->GetBehavior() == Dali::Layer::LAYER_3D,
+ rayTest,
+ results.point,
+ results.eventTime,
+ results.actorLists,
+ true);
}
else if(IsWithinSourceActors(*sourceActor, *layer))
{
// Recursively hit test all the actors, without crossing into other layers.
hit = HitTestWithinLayer(*layer,
- renderTask,
- exclusives,
- results.rayOrigin,
- results.rayDirection,
- nearClippingPlane,
- farClippingPlane,
- hitCheck,
- overlayHit,
- overlayHit,
- layer->GetBehavior() == Dali::Layer::LAYER_3D,
- rayTest,
- results.point,
- results.eventTime,
- results.actorLists,
- true);
+ renderTask,
+ exclusives,
+ results.rayOrigin,
+ results.rayDirection,
+ nearClippingPlane,
+ farClippingPlane,
+ hitCheck,
+ overlayHit,
+ overlayHit,
+ layer->GetBehavior() == Dali::Layer::LAYER_3D,
+ rayTest,
+ results.point,
+ results.eventTime,
+ results.actorLists,
+ true);
}
}
const Vector2& screenCoordinates,
Results& results,
HitTestInterface& hitCheck,
- bool isGeometry)
+ bool isGeometry)
{
if(isGeometry)
{
- RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
- RenderTaskList::RenderTaskContainer::iterator endIter = tasks.end();
- const auto& exclusives = taskList.GetExclusivesList();
- RayTest rayTest;
+ RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
+ RenderTaskList::RenderTaskContainer::iterator endIter = tasks.end();
+ const auto& exclusives = taskList.GetExclusivesList();
+ RayTest rayTest;
// Hit test order should be of draw order
for(RenderTaskList::RenderTaskContainer::iterator iter = tasks.begin(); endIter != iter; ++iter)
const Vector2& screenCoordinates,
Results& results,
HitTestInterface& hitCheck,
- bool isGeometry)
+ bool isGeometry)
{
bool result = false;
return HitTest(sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck, isGeometry);
}
-} // namespace HitTestAlgorithm
-
-} // namespace Internal
-
-} // namespace Dali
+} // namespace Dali::Internal::HitTestAlgorithm