How to select instant3Dhub-Geometry with LineTraces

In this guide we will implement a C++ function to select instant3Dhub geometry by tracing a ray against the world to hit instant3Dhub Geometry Actors.

We assume there’s a HubConnector connected to an instant3Dhub session. Additionally, a start point (FVector RayStart) and end point (FVector RayEnd) of a ray is given that is used for the LineTrace.

Below is the function scaffold that will be filled with logic in the following steps:

void SelectGeometryWithLineTrace(AI3DHConnector* HubConnector, FVector RayStart, FVector RayEnd)
{
    check(IsValid(HubConnector));
    check(HubConnector->IsConnectedToHub());

    // 1. Constrain the Ray by ClipPlanes
    // 2. Find instant3Dhub Geometry hit by a Ray
    // 3. Select the Geometry Actor
}

1. Constrain the Ray by ClipPlanes

To prevent selection of geometry that is hidden we need to constrain the ray to the space that is not clipped. We provide a utility function that does that for you.

// 1. Constrain the Ray by ClipPlanes
if (!UI3DHUtilities::ClipLineSegmentByClipPlanes(HubConnector, RayStart, RayEnd))
{
    // Early return, because the ray is completely inside the clipped space
    return;
}

2. Find instant3Dhub Geometry hit by a Ray

Next we trace a ray against the world using the instant3Dhub Geometry Profile. With the default profile configuration this will trace a ray against all blocking visible geometry which might contain other Actors in the world. We are only interested in Actors of the class AI3DHGeometry.

Tip

Unreal Engine offers different ways to do spatial queries in the world. With Collision Profiles you have full control over the collision response of instant3Dhub Geometry. You are not limited to LineTraceSingleByProfile and we encourage you to take a look at other available functions including but not limited to LineTraceSingleByChannel, LineTraceMultiByProfile, SweepSingleByObjectType.

// 2. Find instant3Dhub Geometry hit by a Ray
AI3DHGeometry* GeometryActor = nullptr;
{
    UWorld* World = HubConnector->GetWorld();
    FHitResult HitResult;

    if (World->LineTraceSingleByProfile(HitResult, RayStart, RayEnd, UInstant3DHubSettings::GetChecked().GeometryCollisionProfileName))
    {
        GeometryActor = Cast<AI3DHGeometry>(HitResult.GetActor());
    }
}

if (GeometryActor == nullptr)
{
    // Early return, because no instant3Dhub Geometry was hit
    return;
}

3. Select the Geometry Actor

After a Geometry Actor was found we can get its resource NodeId in the instant3Dhub tree. The NodeId can then be added to the Selection. In this example we will first clear the active Selection. Once cleared, the NodeId will be added to the Selection, making it the only Node that is selected.

API calls that are synchronized with the instant3Dhub session typically expect an OnComplete Callback Function. The bound function is always called on the GameThread with an appropriate ErrorCode. In this example, we have a nested setup that first clears the selection and, once this operation is completed, selects the Geometry using its NodeId.

Note

Please note that both ClearSelection and AddToSelection are not blocking. They are asynchronous function calls executed in another thread.

// 3. Select the Geometry Actor
const int32 NodeId = GeometryActor->GetNodeId();
TSharedPtr<FI3DHConnectorAPI> API = HubConnector->GetAPI();
API->GetSelectionAPI()->ClearSelection([API, NodeId](EI3DHErrorCode ErrorCode)
{
    API->GetSelectionAPI()->AddToSelection(NodeId, [](const TArray<int32>& Selection, EI3DHErrorCode ErrorCode) 
    {
        if (ErrorCode == EI3DHErrorCode::Success)
        {
            UE_LOG(LogTemp, Verbose, TEXT("Successfully Selected NodeId: '%d'"), NodeId);
        }
        else
        {
            UE_LOG(LogTemp, Warning, TEXT("ErrorCode '%s': Failed to select NodeId: '%d'"), *I3DHStringFromErrorCode(ErrorCode), NodeId);
        }
    });
});

Final result

Putting it all together, this is final result of our SelectGeometryWithLineTrace function:

void SelectGeometryWithLineTrace(AI3DHConnector* HubConnector, FVector RayStart, FVector RayEnd)
{
    check(IsValid(HubConnector));
    check(HubConnector->IsConnectedToHub());

    // 1. Constrain the Ray by ClipPlanes
    if (!UI3DHUtilities::ClipLineSegmentByClipPlanes(HubConnector, RayStart, RayEnd))
    {
        // Early return, because the ray is completely inside the clipped space
        return;
    }

    // 2. Find instant3Dhub Geometry hit by a Ray
    AI3DHGeometry* GeometryActor = nullptr;
    UWorld* World = HubConnector->GetWorld();
    FHitResult HitResult;

    if (World->LineTraceSingleByProfile(HitResult, RayStart, RayEnd, UInstant3DHubSettings::GetChecked().GeometryCollisionProfileName))
    {
        GeometryActor = Cast<AI3DHGeometry>(HitResult.GetActor());
    }

    if (GeometryActor == nullptr)
    {
        // Early return, because no instant3Dhub Geometry was hit
        return;
    }

    // 3. Select the Geometry Actor
    const int32 NodeId = GeometryActor->GetNodeId();
    TSharedPtr<FI3DHConnectorAPI> API = HubConnector->GetAPI();
    API->GetSelectionAPI()->ClearSelection([API, NodeId](EI3DHErrorCode ErrorCode)
    {
        API->GetSelectionAPI()->AddToSelection(NodeId, [](const TArray<int32>& Selection, EI3DHErrorCode ErrorCode) 
        {
            if (ErrorCode == EI3DHErrorCode::Success)
            {
                UE_LOG(LogTemp, Verbose, TEXT("Successfully Selected NodeId: '%d'"), NodeId);
            }
            else
            {
                UE_LOG(LogTemp, Warning, TEXT("ErrorCode '%s': Failed to select NodeId: '%d'"), *I3DHStringFromErrorCode(ErrorCode), NodeId);
            }
        });
    });
}