/*
- * Copyright (c) 2014 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2016 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.
// INTERNAL INCLUDES
#include <dali/integration-api/system-overlay.h>
+#include <dali/public-api/actors/layer.h>
#include <dali/public-api/math/vector2.h>
#include <dali/public-api/math/vector4.h>
#include <dali/integration-api/debug.h>
#include <dali/internal/event/actors/image-actor-impl.h>
#include <dali/internal/event/actors/layer-impl.h>
#include <dali/internal/event/actors/layer-list.h>
-#include <dali/internal/event/actors/renderable-actor-impl.h>
#include <dali/internal/event/common/system-overlay-impl.h>
#include <dali/internal/event/common/stage-impl.h>
#include <dali/internal/event/common/projection.h>
{
HitActor()
: actor( NULL ),
- x( 0 ),
- y( 0 ),
distance( std::numeric_limits<float>::max() ),
depth( std::numeric_limits<int>::min() )
{
}
- Actor *actor; ///< the actor hit. (if actor hit, then initialised)
- float x; ///< x position of hit (only valid if actor valid)
- float y; ///< y position of hit (only valid if actor valid)
- float distance; ///< distance from ray origin to hit actor
- int depth; ///< depth index of this actor
-
+ Actor *actor; ///< The actor hit (if actor is hit, then this is initialised).
+ Vector2 hitPosition; ///< Position of hit (only valid if actor valid).
+ float distance; ///< Distance from ray origin to hit actor.
+ int depth; ///< Depth index of this actor.
};
/**
const Vector< RenderTaskList::Exclusive >& exclusives )
{
- if ( exclusives.Size() )
+ if( exclusives.Size() )
{
- for ( Vector< RenderTaskList::Exclusive >::Iterator exclusiveIt = exclusives.Begin(); exclusives.End() != exclusiveIt; ++exclusiveIt )
+ for( Vector< RenderTaskList::Exclusive >::Iterator exclusiveIt = exclusives.Begin(); exclusives.End() != exclusiveIt; ++exclusiveIt )
{
- if ( exclusiveIt->renderTaskPtr != &renderTask )
+ if( ( exclusiveIt->renderTaskPtr != &renderTask ) && ( exclusiveIt->actorPtr == &actor ) )
{
- if ( exclusiveIt->actorPtr == &actor )
- {
- return true;
- }
+ return true;
}
}
}
float& nearClippingPlane,
float& farClippingPlane,
HitTestInterface& hitCheck,
- bool& stencilOnLayer,
- bool& stencilHit,
- bool parentIsStencil )
+ bool& overlayHit,
+ bool layerIs3d,
+ unsigned int clippingDepth,
+ unsigned int clippingBitPlaneMask )
{
HitActor hit;
- if ( IsActorExclusiveToAnotherRenderTask( actor, renderTask, exclusives ) )
+ if( IsActorExclusiveToAnotherRenderTask( actor, renderTask, exclusives ) )
{
return hit;
}
- // Children should inherit the stencil draw mode
- bool isStencil = parentIsStencil;
-
- if ( actor.GetDrawMode() == DrawMode::STENCIL && actor.IsVisible() )
+ // For clipping, regardless of whether we have hit this actor or not,
+ // we increase the clipping depth if we have hit a clipping actor.
+ // This is used later to ensure all nested clipped children have hit
+ // all clipping actors also for them to be counted as hit.
+ unsigned int newClippingDepth = clippingDepth;
+ bool clippingActor = actor.GetClippingMode() != ClippingMode::DISABLED;
+ if( clippingActor )
{
- isStencil = true;
- stencilOnLayer = true;
+ ++newClippingDepth;
}
- // If we are a stencil or hittable...
- if ( isStencil || hitCheck.IsActorHittable( &actor ) )
+ // If we are a clipping actor or hittable...
+ if( clippingActor || hitCheck.IsActorHittable( &actor ) )
{
Vector3 size( actor.GetCurrentSize() );
- if ( size.x > 0.0f && size.y > 0.0f && // Ensure the actor has a valid size.
- actor.RaySphereTest( rayOrigin, rayDir ) ) // Perform quicker ray sphere test to see if our ray is close to the actor.
+ // Ensure the actor has a valid size.
+ // If so, perform a quick ray sphere test to see if our ray is close to the actor.
+ if( size.x > 0.0f && size.y > 0.0f && actor.RaySphereTest( rayOrigin, rayDir ) )
{
- Vector4 hitPointLocal;
+ Vector2 hitPointLocal;
float distance;
// Finally, perform a more accurate ray test to see if our ray actually hits the actor.
{
if( distance >= nearClippingPlane && distance <= farClippingPlane )
{
- // If the hit has happened on a stencil then register, but don't record as hit result
- if ( isStencil )
+ // If the hit has happened on a clipping actor, then add this clipping depth to the mask of hit clipping depths.
+ // This mask shows all the actors that have been hit at different clipping depths.
+ if( clippingActor )
{
- stencilHit = true;
+ clippingBitPlaneMask |= 1u << clippingDepth;
+ }
+
+ if( overlayHit && !actor.IsOverlay() )
+ {
+ // If we have already hit an overlay and current actor is not an overlay ignore current actor.
}
else
{
- hit.actor = &actor;
- hit.x = hitPointLocal.x;
- hit.y = hitPointLocal.y;
- hit.distance = distance;
-
- // Is this actor an Image Actor or contains a renderer?
- if ( ImageActor* imageActor = dynamic_cast< ImageActor* >( &actor ) )
+ if( actor.IsOverlay() )
{
- hit.depth = imageActor->GetDepthIndex();
+ overlayHit = true;
}
- else
+
+ // At this point we have hit an actor.
+ // Now perform checks for clipping.
+ // Assume we have hit the actor first as if it is not clipped this would be the case.
+ bool haveHitActor = true;
+
+ // Check if we are performing clipping. IE. if any actors so far have clipping enabled - not necessarily this one.
+ // We can do this by checking the clipping depth has a value 1 or above.
+ if( newClippingDepth >= 1u )
{
- if ( actor.GetRendererCount() )
+ // Now for us to count this actor as hit, we must have also hit
+ // all CLIPPING actors up to this point in the hierarchy as well.
+ // This information is stored in the clippingBitPlaneMask we updated above.
+ // Here we calculate a comparison mask by setting all the bits up to the current depth value.
+ // EG. a depth of 4 (10000 binary) = a mask of 1111 binary.
+ // This allows us a fast way of comparing all bits are set up to this depth.
+ unsigned int clippingDepthMask = ( 1u << clippingDepth ) - 1u;
+
+ // The two masks must be equal to be a hit, as we are already assuming a hit
+ // (for non-clipping mode) then they must be not-equal to disqualify the hit.
+ if( clippingBitPlaneMask != clippingDepthMask )
{
- hit.depth = actor.GetRendererAt( 0 ).GetDepthIndex();
+ haveHitActor = false;
}
- else
+ }
+
+ if( haveHitActor )
+ {
+ hit.actor = &actor;
+ hit.hitPosition = hitPointLocal;
+ hit.distance = distance;
+ hit.depth = actor.GetHierarchyDepth() * Dali::Layer::TREE_DEPTH_MULTIPLIER;
+
+ if( actor.GetRendererCount() > 0 )
{
- hit.depth = 0;
+ //Get renderer with maximum depth
+ int rendererMaxDepth(actor.GetRendererAt( 0 ).Get()->GetDepthIndex());
+ for( unsigned int i(1); i < actor.GetRendererCount(); ++i )
+ {
+ int depth = actor.GetRendererAt( i ).Get()->GetDepthIndex();
+ if( depth > rendererMaxDepth )
+ {
+ rendererMaxDepth = depth;
+ }
+ }
+ hit.depth += rendererMaxDepth;
}
}
}
}
}
- // 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
- if ( isStencil && stencilHit )
- {
- return hit;
- }
-
// Find a child hit, until we run out of actors in the current layer.
HitActor childHit;
if( actor.GetChildCount() > 0 )
for( ActorIter iter = children.begin(), endIter = children.end(); iter != endIter; ++iter )
{
// Descend tree only if...
- 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
- ( isStencil || hitCheck.DescendActorHierarchy( &GetImplementation( *iter ) ) ) ) // We are a stencil OR we can descend into child hierarchy
+ if ( !( *iter )->IsLayer() && // Child is NOT a layer, hit testing current layer only
+ ( hitCheck.DescendActorHierarchy( ( *iter ).Get() ) ) ) // We can descend into child hierarchy
{
- HitActor currentHit( HitTestWithinLayer( GetImplementation(*iter),
- renderTask,
- exclusives,
- rayOrigin,
- rayDir,
- nearClippingPlane,
- farClippingPlane,
- hitCheck,
- stencilOnLayer,
- stencilHit,
- isStencil ) );
+ HitActor currentHit( HitTestWithinLayer( ( *iter->Get() ),
+ renderTask,
+ exclusives,
+ rayOrigin,
+ rayDir,
+ nearClippingPlane,
+ farClippingPlane,
+ hitCheck,
+ overlayHit,
+ layerIs3d,
+ newClippingDepth,
+ clippingBitPlaneMask ) );
bool updateChildHit = false;
- // If our ray casting hit, then check then if the hit actor's depth is greater that the favorite, it will be preferred
- if ( currentHit.distance >= 0.0f )
+ if( currentHit.distance >= 0.0f )
{
- if ( currentHit.depth > childHit.depth )
+ if( layerIs3d )
{
- updateChildHit = true;
+ updateChildHit = ( ( currentHit.depth > childHit.depth ) ||
+ ( ( currentHit.depth == childHit.depth ) && ( currentHit.distance < childHit.distance ) ) );
}
-
- // If the hit actor's depth is equal to current favorite, then we check the distance and prefer the closer
- else if ( currentHit.depth == childHit.depth )
+ else
{
- if ( currentHit.distance < childHit.distance )
- {
- updateChildHit = true;
- }
+ updateChildHit = currentHit.depth >= childHit.depth;
}
}
- if ( updateChildHit )
+ if( updateChildHit )
{
if( !parentIsRenderable || currentHit.depth > hit.depth ||
- ( currentHit.depth == hit.depth && currentHit.distance < hit.distance ) )
- {
- childHit = currentHit;
- }
+ ( layerIs3d && ( currentHit.depth == hit.depth && currentHit.distance < hit.distance )) )
+ {
+ childHit = currentHit;
+ }
}
}
}
}
+
return ( childHit.actor ) ? childHit : hit;
}
{
return true;
}
- else
+
+ Actor* parent = actor.GetParent();
+ if ( parent )
{
- Actor* parent = actor.GetParent();
- if ( parent )
- {
- return IsWithinSourceActors( sourceActor, *parent );
- }
+ return IsWithinSourceActors( sourceActor, *parent );
}
// Not within source actors
{
bool hittable( true );
- if(layer.IsClipping())
+ if( layer.IsClipping() )
{
ClippingBox box = layer.GetClippingBox();
}
}
- if(hittable)
+ if( hittable )
{
Actor* actor( &layer );
// Ensure that we can descend into the layer's (or any of its parent's) hierarchy.
- while ( actor && hittable )
+ while( actor && hittable )
{
- if ( ! hitCheck.DescendActorHierarchy( actor ) )
+ if( ! hitCheck.DescendActorHierarchy( actor ) )
{
hittable = false;
break;
}
float nearClippingPlane, farClippingPlane;
- GetCameraClippingPlane(renderTask, nearClippingPlane, farClippingPlane);
+ GetCameraClippingPlane( renderTask, nearClippingPlane, farClippingPlane );
// Determine the layer depth of the source actor
Actor* sourceActor( renderTask.GetSourceActor() );
- if ( sourceActor )
+ if( sourceActor )
{
Dali::Layer layer( sourceActor->GetLayer() );
- if ( layer )
+ if( layer )
{
const unsigned int sourceActorDepth( layer.GetDepth() );
// Hit test starting with the top layer, working towards the bottom layer.
HitActor hit;
- bool stencilOnLayer = false;
- bool stencilHit = false;
+ bool overlayHit = false;
bool layerConsumesHit = false;
-
const Vector2& stageSize = stage.GetSize();
- for (int i=layers.GetLayerCount()-1; i>=0 && !(hit.actor); --i)
+ for( int i = layers.GetLayerCount() - 1; i >= 0 && !( hit.actor ); --i )
{
- Layer* layer( layers.GetLayer(i) );
- HitActor previousHit = hit;
- stencilOnLayer = false;
- stencilHit = false;
+ Layer* layer( layers.GetLayer( i ) );
+ overlayHit = false;
// Ensure layer is touchable (also checks whether ancestors are also touchable)
- if ( IsActuallyHittable ( *layer, screenCoordinates, stageSize, hitCheck ) )
+ if( IsActuallyHittable( *layer, screenCoordinates, stageSize, hitCheck ) )
{
// Always hit-test the source actor; otherwise test whether the layer is below the source actor in the hierarchy
- if ( sourceActorDepth == static_cast<unsigned int>(i) )
+ if( sourceActorDepth == static_cast<unsigned int>( i ) )
{
// Recursively hit test the source actor & children, without crossing into other layers.
hit = HitTestWithinLayer( *sourceActor,
nearClippingPlane,
farClippingPlane,
hitCheck,
- stencilOnLayer,
- stencilHit,
- false );
+ overlayHit,
+ layer->GetBehavior() == Dali::Layer::LAYER_3D,
+ 0u,
+ 0u );
}
- else if ( IsWithinSourceActors( *sourceActor, *layer ) )
+ else if( IsWithinSourceActors( *sourceActor, *layer ) )
{
// Recursively hit test all the actors, without crossing into other layers.
hit = HitTestWithinLayer( *layer,
nearClippingPlane,
farClippingPlane,
hitCheck,
- stencilOnLayer,
- stencilHit,
- false );
- }
-
- // If a stencil on this layer hasn't been hit, then discard hit results for this layer if our current hit actor is renderable
- if ( stencilOnLayer && !stencilHit &&
- hit.actor && hit.actor->IsRenderable() )
- {
- hit = previousHit;
+ overlayHit,
+ layer->GetBehavior() == Dali::Layer::LAYER_3D,
+ 0u,
+ 0u );
}
// If this layer is set to consume the hit, then do not check any layers behind it
- if ( hitCheck.DoesLayerConsumeHit( layer ) )
+ if( hitCheck.DoesLayerConsumeHit( layer ) )
{
layerConsumesHit = true;
break;
}
}
}
- if ( hit.actor )
+
+ if( hit.actor )
{
- results.renderTask = Dali::RenderTask(&renderTask);
- results.actor = Dali::Actor(hit.actor);
- results.actorCoordinates.x = hit.x;
- results.actorCoordinates.y = hit.y;
+ results.renderTask = Dali::RenderTask( &renderTask );
+ results.actor = Dali::Actor( hit.actor );
+ results.actorCoordinates = hit.hitPosition;
+
return true; // Success
}
- else if ( layerConsumesHit )
+
+ if( layerConsumesHit )
{
return true; // Also success if layer is consuming the hit
}
}
/**
- * Iterate through RenderTaskList and perform hit test.
+ * Iterate through the RenderTaskList and perform hit testing.
*
- * @return true if we have a hit, false otherwise
+ * @param[in] stage The stage the tests will be performed in
+ * @param[in] layers The list of layers to test
+ * @param[in] taskList The list of render tasks
+ * @param[out] results Ray information calculated by the camera
+ * @param[in] hitCheck The hit testing interface object to use
+ * @param[in] onScreen True to test on-screen, false to test off-screen
+ * @return True if we have a hit, false otherwise
*/
-bool HitTestForEachRenderTask( Stage& stage,
- LayerList& layers,
- RenderTaskList& taskList,
- const Vector2& screenCoordinates,
- Results& results,
- HitTestInterface& hitCheck )
+bool HitTestRenderTaskList( Stage& stage,
+ LayerList& layers,
+ RenderTaskList& taskList,
+ const Vector2& screenCoordinates,
+ Results& results,
+ HitTestInterface& hitCheck,
+ bool onScreen )
{
RenderTaskList::RenderTaskContainer& tasks = taskList.GetTasks();
RenderTaskList::RenderTaskContainer::reverse_iterator endIter = tasks.rend();
-
const Vector< RenderTaskList::Exclusive >& exclusives = taskList.GetExclusivesList();
- // Check onscreen tasks before offscreen ones, hit test order should be reverse of draw order (see ProcessRenderTasks() where offscreen tasks are drawn first).
-
- // on screen
- for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
+ for( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
{
RenderTask& renderTask = GetImplementation( *iter );
Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
// Note that if frameBufferImage is NULL we are using the default (on screen) render target
- if(frameBufferImage)
+ if( frameBufferImage )
{
- ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
+ ResourceId id = GetImplementation( frameBufferImage ).GetResourceId();
- // on screen only
- if(0 != id)
+ // Change comparison depending on if on-screen or off-screen.
+ if( onScreen ? ( 0 != id ) : ( 0 == id ) )
{
// Skip to next task
continue;
}
}
- if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
+ if( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
{
// Return true when an actor is hit (or layer in our render-task consumes the hit)
return true; // don't bother checking off screen tasks
}
}
- // off screen
- for ( RenderTaskList::RenderTaskContainer::reverse_iterator iter = tasks.rbegin(); endIter != iter; ++iter )
- {
- RenderTask& renderTask = GetImplementation( *iter );
- Dali::FrameBufferImage frameBufferImage = renderTask.GetTargetFrameBuffer();
-
- // Note that if frameBufferImage is NULL we are using the default (on screen) render target
- if(frameBufferImage)
- {
- ResourceId id = GetImplementation(frameBufferImage).GetResourceId();
+ return false;
+}
- // off screen only
- if(0 == id)
- {
- // Skip to next task
- continue;
- }
+/**
+ * Iterate through the RenderTaskList and perform hit testing for both on-screen and off-screen.
+ *
+ * @param[in] stage The stage the tests will be performed in
+ * @param[in] layers The list of layers to test
+ * @param[in] taskList The list of render tasks
+ * @param[out] results Ray information calculated by the camera
+ * @param[in] hitCheck The hit testing interface object to use
+ * @param[in] onScreen True to test on-screen, false to test off-screen
+ * @return True if we have a hit, false otherwise
+ */
+bool HitTestForEachRenderTask( Stage& stage,
+ LayerList& layers,
+ RenderTaskList& taskList,
+ const Vector2& screenCoordinates,
+ Results& results,
+ HitTestInterface& hitCheck )
+{
+ bool result = false;
- if ( HitTestRenderTask( exclusives, stage, layers, renderTask, screenCoordinates, results, hitCheck ) )
- {
- // Return true when an actor is hit (or a layer in our render-task consumes the hit)
- return true;
- }
- }
+ // Check on-screen tasks before off-screen ones.
+ // Hit test order should be reverse of draw order (see ProcessRenderTasks() where off-screen tasks are drawn first).
+ if( HitTestRenderTaskList( stage, layers, taskList, screenCoordinates, results, hitCheck, true ) ||
+ HitTestRenderTaskList( stage, layers, taskList, screenCoordinates, results, hitCheck, false ) )
+ {
+ // Found hit.
+ result = true;
}
- return false;
+
+ return result;
}
} // unnamed namespace
Results hitTestResults;
HitTestFunctionWrapper hitTestFunctionWrapper( func );
- if ( HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
+ if( HitTestForEachRenderTask( stage, layerList, taskList, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
{
results.actor = hitTestResults.actor;
results.actorCoordinates = hitTestResults.actorCoordinates;
// Hit-test the system-overlay actors first
SystemOverlay* systemOverlay = stage.GetSystemOverlayInternal();
- if ( systemOverlay )
+ if( systemOverlay )
{
RenderTaskList& overlayTaskList = systemOverlay->GetOverlayRenderTasks();
LayerList& overlayLayerList = systemOverlay->GetLayerList();
}
// Hit-test the regular on-stage actors
- if ( !wasHit )
+ if( !wasHit )
{
RenderTaskList& taskList = stage.GetRenderTaskList();
LayerList& layerList = stage.GetLayerList();
bool wasHit( false );
Results hitTestResults;
- const Vector< RenderTaskList::Exclusive >& exclusives = Stage::GetCurrent()->GetRenderTaskList().GetExclusivesList();
+ const Vector< RenderTaskList::Exclusive >& exclusives = stage.GetRenderTaskList().GetExclusivesList();
HitTestFunctionWrapper hitTestFunctionWrapper( func );
- if ( HitTestRenderTask( exclusives, stage, stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
+ if( HitTestRenderTask( exclusives, stage, stage.GetLayerList(), renderTask, screenCoordinates, hitTestResults, hitTestFunctionWrapper ) )
{
results.actor = hitTestResults.actor;
results.actorCoordinates = hitTestResults.actorCoordinates;
return wasHit;
}
+
} // namespace HitTestAlgorithm
} // namespace Internal