Thanks for the response, Sam!
Unfortunately, I believe I am either misunderstanding a part of the steps you mentioned, or perhaps it is still not working for a different reason. Before I continue to investigate, I figured I should go ahead a post what I have locally so that you are able to take a look sooner rather than later. I will follow up on this post if I am to find where I am going wrong.
Below is what I have right now, and it all lives within the action mapping of a Level Editor Viewport Right-Click context menu. The only differences between what I have and what you sent me is that I put it all in this one section (I understand it’s not good programming practices, but this is just temporary as I am investigating the possibility of this tool. I will clean it up and refactor it afterwards), and I also do not use the player controller (since we are in the editor).
Unfortunately, I will not be able to provide an example problematic mesh for privacy reasons.
Another note/update: I have spoken with some colleagues, and some of them have suggested that instead of the raycast hitting the complex collision (which they explained does not have a material assigned), they think I could be hitting the Nanite Fallback mesh, which is apparently where the complex collision is derived from. I just wanted to call this out in case this information is helpful to you.
I will keep investigating further to try and get this figured out. If you have any ideas to help me further, or if you see any issues with the code snippet I have pasted below (much of which was piecing together what you have previously suggested me), please do let me know. I feel like we are getting close to the solution!
FVector OutWorldPosition, OutWorldDirection;
if (FLevelEditorViewportClient* LevelViewportClient = GCurrentLevelEditingViewportClient)
{
if (FViewport* ActiveViewport = LevelViewportClient->Viewport)
{
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
ActiveViewport,
LevelViewportClient->GetScene(),
LevelViewportClient->EngineShowFlags)
.SetRealtimeUpdate(true));
FSceneView* View = LevelViewportClient->CalcSceneView(&ViewFamily);
FIntPoint MousePos;
LevelViewportClient->Viewport->GetMousePos(MousePos);
const FIntPoint ViewportSize = ActiveViewport->GetSizeXY();
const FIntRect ViewRect = FIntRect(0, 0, ViewportSize.X, ViewportSize.Y);
const FMatrix InvViewProjectionMatrix = View->ViewMatrices.GetInvViewProjectionMatrix();
FSceneView::DeprojectScreenToWorld(MousePos, ViewRect, InvViewProjectionMatrix, OutWorldPosition, OutWorldDirection);
// 1) perform a "simple" line trace from the mouse pointer into the scene(use a FCollisionQueryParams struct with bTraceComplex set to false)
FHitResult HitResult;
double TraceDistance = 10000000;
FCollisionQueryParams CollisionQueryParams;
CollisionQueryParams.bReturnFaceIndex = true;
CollisionQueryParams.bTraceComplex = false;
UWorld* World = GEditor->GetEditorWorldContext().World();
World->LineTraceSingleByChannel(HitResult, OutWorldPosition, OutWorldPosition + OutWorldDirection * TraceDistance, ECollisionChannel::ECC_Visibility, CollisionQueryParams);
const AActor* HitActor = HitResult.GetActor();
if (HitActor)
{
UStaticMeshComponent* StaticMeshComp = HitActor->FindComponentByClass<UStaticMeshComponent>();
if (StaticMeshComp)
{
UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh();
// save LODForCollision value
int32 OriginalLODForCollision = StaticMesh->LODForCollision;
//2) if a static mesh is hit, estimate its LOD with the code above and temporarily change its "LOD for Collision" to the estimated LOD
const FStaticMeshRenderData* RenderData = StaticMeshComp->GetStaticMesh()->GetRenderData();
int32 LODIndexToUse = 0;
if (RenderData && RenderData->LODResources.Num() != 0)
{
// Calculate screen size
FVector Origin;
FVector BoxExtent;
StaticMeshComp->GetLocalBounds(Origin, BoxExtent);
float Radius = BoxExtent.Size();
FVector MeshLocation = StaticMeshComp->GetComponentLocation();
float Distance = FVector::Dist(OutWorldPosition, MeshLocation);
// Compute screen size
const float ScreenWidth = ActiveViewport->GetSizeXY().X;
const float ScreenScale = Radius / Distance;
float CalculatedScreenSize = ScreenScale * ScreenWidth;
// Get screen size thresholds
const TArray<FStaticMeshSourceModel>& SourceModels = StaticMeshComp->GetStaticMesh()->GetSourceModels();
for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); ++LODIndex)
{
float ScreenSize = SourceModels[LODIndex].ScreenSize.Default;
// Compare against thresholds
if (CalculatedScreenSize >= ScreenSize)
{
LODIndexToUse = LODIndex;
break;
}
}
}
StaticMesh->LODForCollision = LODIndexToUse;
// 3) perform a more accurate "complex" line trace(as in the first code sample) against the collision LOD mesh
CollisionQueryParams.bTraceComplex = true;
World->LineTraceSingleByChannel(HitResult, OutWorldPosition, OutWorldPosition + OutWorldDirection * TraceDistance, ECollisionChannel::ECC_Visibility, CollisionQueryParams);
const FStaticMeshLODResources& LODResource = StaticMesh->GetRenderData()->LODResources[LODIndexToUse];
// The triangle index you are looking for
int32 TriangleIndex = HitResult.FaceIndex;
// The index buffer is a flat array of vertex indices. Each triangle uses 3 indices.
// The "global" index of the first vertex of the triangle in the index buffer.
uint32 GlobalIndexStart = TriangleIndex * 3;
DrawDebugPoint(World, HitResult.Location, 10, FColor(255, 0, 0), true, 30, 0);
// Iterate through all sections in the LOD to find which one contains this index range
for (int32 SectionIndex = 0; SectionIndex < LODResource.Sections.Num(); ++SectionIndex)
{
const FStaticMeshSection& Section = LODResource.Sections[SectionIndex];
// Check if our triangle's starting index falls within this section's index buffer range
uint32 SectionFirstIndex = Section.FirstIndex;
uint32 SectionLastIndex = SectionFirstIndex + Section.NumTriangles * 3;
if (GlobalIndexStart >= SectionFirstIndex && GlobalIndexStart < SectionLastIndex)
{
// Found the section! The triangle belongs to this SectionIndex
// You can now access section-specific data, like the Material
UMaterialInterface* SectionMaterial = StaticMeshComp->GetMaterial(Section.MaterialIndex);
if (SectionMaterial)
{
UE_LOG(LogTemp, Warning, TEXT("Static mesh name: %s Section material name: %s"), *(StaticMesh->GetName()), *(SectionMaterial->GetName()));
}
}
}
// 4) revert the mesh's LOD for Collision setting back to its original value
StaticMesh->LODForCollision = OriginalLODForCollision;
}
}
}
}