How to select instant3Dhub-Geometry with LineTraces
In this guide we will implement a C++ function that selects instant3Dhub geometry by tracing a ray against the world and then analyzing the hit result.
We assume there is an HubConnector that is already 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 a 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 clip planes
// 2. Trace and analyze the hit
// 3. Select the geometry that was hit
}
1. Constrain the ray by clip planes
To prevent selection of geometry that is currently hidden we need to constrain the ray to the space that is not clipped. A utility function takes care of this:
// 1. Constrain the ray by clip planes
if (!UI3DHUtilities::ClipLineSegmentByClipPlanes(HubConnector, RayStart, RayEnd))
{
// The ray lies completely inside the clipped space – nothing to select.
return;
}
2. Trace and analyze the hit
Next we trace a ray against the world using the
instant3Dhub Geometry Profile. The trace might hit any blocking, visible geometry in the world, so afterwards we pass the raw FHitResult to UI3DHUtilities::AnalyzeHitResult. The helper returns true and fills an FI3DHHitResult when the hit was produced by instant3Dhub geometry.
// 2. Trace and analyze the hit
FI3DHHitResult I3DHHit; // holds instant3Dhub‑specific hit data
UWorld* World = HubConnector->GetWorld();
FHitResult HitResult;
if (!World->LineTraceSingleByProfile(HitResult,
RayStart,
RayEnd,
UInstant3DHubSettings::GetChecked().GeometryCollisionProfileName))
{
return; // the trace didn't hit anything
}
if (!UI3DHUtilities::AnalyzeHitResult(HitResult, I3DHHit))
{
return; // an object was hit, but it is NOT instant3Dhub geometry
}
Tip
AnalyzeHitResult works with any method that produces an FHitResult. LineTraceSingleByChannel, SweepSingleByObjectType, etc. Feel free to use whatever suits your use case.
3. Select the geometry
FI3DHHitResult contains everything we need to identify the node of the hit geometry. The DetailNodeId corresponds to the smallest addressable Node of the geometry that was hit.
The NodeId can now 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.
// 3. Select the geometry
const int32 NodeId = I3DHHit.DetailNodeId;
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("%s – failed to select NodeId %d"),
*I3DHStringFromErrorCode(ErrorCode), NodeId);
}
});
});
Note
Both FI3DHSelectionAPI::ClearSelection and FI3DHSelectionAPI::AddToSelection are asynchronous and run on a background thread. The provided lambdas are executed on the game thread when the operation completes.
Final result
Putting it all together, the updated SelectGeometryWithLineTrace looks like this:
void SelectGeometryWithLineTrace(AI3DHConnector* HubConnector,
FVector RayStart,
FVector RayEnd)
{
check(IsValid(HubConnector));
check(HubConnector->IsConnectedToHub());
// 1. Constrain the ray by clip planes
if (!UI3DHUtilities::ClipLineSegmentByClipPlanes(HubConnector, RayStart, RayEnd))
{
// The ray lies completely inside the clipped space – nothing to select.
return;
}
// 2. Trace and analyze hit
FI3DHHitResult I3DHHit;
FHitResult HitResult;
UWorld* World = HubConnector->GetWorld();
if (!World->LineTraceSingleByProfile(HitResult,
RayStart,
RayEnd,
UInstant3DHubSettings::GetChecked().GeometryCollisionProfileName))
{
return; // the trace didn't hit anything
}
if (!UI3DHUtilities::AnalyzeHitResult(HitResult, I3DHHit))
{
return; // an object was hit, but it is NOT instant3Dhub geometry
}
// 3. Select the geometry
const int32 NodeId = I3DHHit.DetailNodeId;
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("%s – failed to select NodeId %d"),
*I3DHStringFromErrorCode(ErrorCode), NodeId);
}
});
});
}
Why not use AI3DHGeometry directly?
You can bypass UI3DHUtilities::AnalyzeHitResult and grab the actor from the raw FHitResult yourself:
FHitResult HitResult;
if (World->LineTraceSingleByProfile(HitResult,
RayStart,
RayEnd,
UInstant3DHubSettings::GetChecked().GeometryCollisionProfileName))
{
AI3DHGeometry* GeometryActor = Cast<AI3DHGeometry>(HitResult.GetActor());
}
While this works, remember that AI3DHGeometry is marked experimental. Using AnalyzeHitResult and the resulting FI3DHHitResult keeps your code independent of the underlying actor implementation and therefore more resilient to future plugin changes.