From 0e47c9fc0d5a77391cda2b4fe513ddcceaed794f Mon Sep 17 00:00:00 2001 From: Adeel Kazmi Date: Thu, 14 Mar 2024 16:01:31 +0000 Subject: [PATCH] [Tizen] (HitTest) Overlay actors should not be clipped if CLIP_TO_BOUNDING_BOX is selected Change-Id: Id76d47a773766e19f969d73bb912e5aa6518380e --- .../src/dali/utc-Dali-HitTestAlgorithm.cpp | 320 ++++++++++++++++++++- .../event/events/hit-test-algorithm-impl.cpp | 196 +++++++------ dali/public-api/actors/actor.h | 6 +- 3 files changed, 438 insertions(+), 84 deletions(-) diff --git a/automated-tests/src/dali/utc-Dali-HitTestAlgorithm.cpp b/automated-tests/src/dali/utc-Dali-HitTestAlgorithm.cpp index 44e8c22..64e7e7d 100644 --- a/automated-tests/src/dali/utc-Dali-HitTestAlgorithm.cpp +++ b/automated-tests/src/dali/utc-Dali-HitTestAlgorithm.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -930,4 +930,322 @@ int UtcDaliHitTestAlgorithmBuildPickingRay02(void) 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::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::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 diff --git a/dali/internal/event/events/hit-test-algorithm-impl.cpp b/dali/internal/event/events/hit-test-algorithm-impl.cpp index e441aeb..fdb45f7 100644 --- a/dali/internal/event/events/hit-test-algorithm-impl.cpp +++ b/dali/internal/event/events/hit-test-algorithm-impl.cpp @@ -1,5 +1,5 @@ /* - * 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. @@ -33,11 +33,7 @@ #include #include -namespace Dali -{ -namespace Internal -{ -namespace HitTestAlgorithm +namespace Dali::Internal::HitTestAlgorithm { namespace { @@ -178,6 +174,25 @@ bool IsActorExclusiveToAnotherRenderTask(const Actor& } /** + * 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, @@ -239,7 +254,7 @@ 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) { @@ -324,18 +339,20 @@ HitActor HitTestWithinLayer(Actor& actor, // 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); @@ -351,7 +368,7 @@ HitActor HitTestWithinLayer(Actor& 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; } @@ -371,21 +388,21 @@ HitActor HitTestWithinLayer(Actor& actor, (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. @@ -399,7 +416,26 @@ HitActor HitTestWithinLayer(Actor& actor, } } - 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; } /** @@ -473,13 +509,13 @@ void GetCameraClippingPlane(RenderTask& renderTask, float& nearClippingPlane, fl } 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)) { @@ -506,18 +542,18 @@ void GeoHitTestRenderTask(const RenderTaskList::ExclusivesContainer& exclusives, if(sourceLayer) { const uint32_t sourceActorDepth(sourceLayer.GetProperty(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) { @@ -533,41 +569,41 @@ void GeoHitTestRenderTask(const RenderTaskList::ExclusivesContainer& exclusives, { // 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); } } @@ -760,14 +796,14 @@ bool HitTestRenderTaskList(const Vector2& sceneSize, 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) @@ -816,7 +852,7 @@ bool HitTestForEachRenderTask(const Vector2& sceneSize, const Vector2& screenCoordinates, Results& results, HitTestInterface& hitCheck, - bool isGeometry) + bool isGeometry) { bool result = false; @@ -867,8 +903,4 @@ bool HitTest(const Vector2& sceneSize, RenderTaskList& renderTaskList, LayerList return HitTest(sceneSize, renderTaskList, layerList, screenCoordinates, results, actorTouchableCheck, isGeometry); } -} // namespace HitTestAlgorithm - -} // namespace Internal - -} // namespace Dali +} // namespace Dali::Internal::HitTestAlgorithm diff --git a/dali/public-api/actors/actor.h b/dali/public-api/actors/actor.h index fe2f5d4..0a58015 100644 --- a/dali/public-api/actors/actor.h +++ b/dali/public-api/actors/actor.h @@ -2,7 +2,7 @@ #define DALI_ACTOR_H /* - * 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. @@ -546,6 +546,8 @@ public: * @brief The draw mode of an actor. * @details Name "drawMode", type DrawMode::Type (Property::INTEGER) or Property::STRING. * @SINCE_1_0.0 + * @note DrawMode::OVERLAY_2D and CLIPPING_MODE set to ClippingMode::CLIP_TO_BOUNDING_BOX cannot be used together. + * In this scenario the clipping is ignored. */ DRAW_MODE, @@ -629,6 +631,8 @@ public: * @details Name "clippingMode", type ClippingMode::Type (Property::INTEGER) or Property::STRING. * @SINCE_1_2_5 * @see ClippingMode::Type for supported values. + * @note ClippingMode::CLIP_TO_BOUNDING_BOX and DRAW_MODE set to DrawMode::OVERLAY_2D cannot be used together. + * In this scenario the clipping is ignored. */ CLIPPING_MODE, -- 2.7.4