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);
}
});
});
}