LineTraceComponent not working for welded bodies

Hi everyone.

The Setup
I have a setup where there is a root mesh component with many child mesh components.
SetSimulatePhysics(true) is called on the root component and the following are called on all the components:
SetCollisionEnabled(ECollisionEnabled::Type::QueryAndPhysics);
SetNotifyRigidBodyCollision(true);

This makes collision work as I expect where if one of the child components hits something it will rotate the whole collection of components together instead of each child component moving independently. Internally my understanding is the BodyInstance of each mesh component is welded to the root component’s BodyInstance.

The Problem
When I call LineTraceComponent on one of the children (or on the parent) it does not properly find the collision. In fact it bails almost right away in FPhysInterface_Chaos::LineTrace_Geom where it calls “FPhysicsCommand::ExecuteRead(InInstance->ActorHandle…” because ActorHandle is nullptr on the child BodyInstances.
If I call LineTraceComponent on the Parent mesh it goes further and gets to iterating over the Shapes later in that function but continues early on all the shapes not associated with the root mesh because it thinks they are not associated with that body:

if (TargetInstance->IsShapeBoundToBody(ShapeRef) == false)
{
	continue;
}

I am using UE 5.1 but I checked the latest source on github and it looks like it works the same way so I don’t expect upgrading will fix it. I think a patch to FBodyInstance::GetOriginalBodyInstance may fix it because it seems that’s where the bug is and I may end up doing that but I need to switch to a custom engine build.

By the way, it looks like this has been written up already here:

But it is closed as Won’t Fix.

Is there a workaround for this? Thanks!

Interesting. Out of curiosity, this occurs with any line trace method, or just line trace component?

can you show the setup and call for you LineTrace ?

could you maybe try a LineTraceSingleByChannel, or LineTraceSingleByProfile (yes this might require defining a Channel or Profile, and then for example

    FHitResult HitResult;
    FCollisionQuaryParams Params;
    Params.AddIgnoredActor(this); // if this is coming out of a component then GetOwner()
#if WITH_EDITOR
    const FName TraceTag("Traces");
    GetWorld()->DebugDrawTraceTag = TraceTag;
    Params.TraceTag = TraceTag;
#endif //WITH_EDITOR
    if( LineTraceSingleByProfile(HitResult, Start, End, FName("MyProfile"), Params)
    {
        if( AActor* Actor = HitResult.GetActor() )
        {
            // this will probably contain the first collider hit that matches the Profile.
            if ( UMyComponent* comp = Cast<UMyComponent>(HitResult.GetComponent()))
           {
                // we have the component
           }
        }
    }

I have tried LineTraceSingleByChannel and it works as I expect - it hits the child objects and the root.
The thing is I’m trying to just trace this one particular mesh component if possible.

I made a small example project showing the issue. Here’s 4 line traces done in different ways. The green ones worked the red ones didn’t. I’ll post my code below as well.

This is an actor I made that is implemented by a blueprint that sets the 2 meshes as cubes to reproduce:

// Header
#pragma once

#include "CoreMinimal.h"
#include "WeldedBody.generated.h"

UCLASS(Blueprintable)
class AWeldedBody : public AActor {
    GENERATED_BODY()

public:
    AWeldedBody(const FObjectInitializer& objInitializer);
    virtual ~AWeldedBody() { }

    virtual void BeginPlay() override;
    virtual void Tick(float deltaSeconds) override;

protected:
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* _rootMesh = nullptr;

    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* _childMesh = nullptr;
};

// Cpp
#include "WeldedBody.h"

AWeldedBody::AWeldedBody(const FObjectInitializer& objInitializer) : Super(objInitializer) {
    PrimaryActorTick.bCanEverTick = true;
	PrimaryActorTick.bStartWithTickEnabled = true;

    _rootMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("RootMesh"));
    SetRootComponent(_rootMesh);

    _childMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ChildMesh"));
    _childMesh->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
}

void AWeldedBody::BeginPlay() {
    Super::BeginPlay();

    _rootMesh->SetSimulatePhysics(true);
    _rootMesh->SetEnableGravity(true);
    _rootMesh->SetCollisionEnabled(ECollisionEnabled::Type::QueryAndPhysics);
    _rootMesh->SetNotifyRigidBodyCollision(true);

    _childMesh->SetCollisionEnabled(ECollisionEnabled::Type::QueryAndPhysics);
    _childMesh->SetNotifyRigidBodyCollision(true);
}

void AWeldedBody::Tick(float deltaSeconds) {
    Super::Tick(deltaSeconds);

    // Record some convenient locations
    FVector childLocation = _childMesh->GetComponentTransform().GetLocation();
    FVector topOfChild = childLocation + 200 * FVector(0, 0, 1);
    FVector rootLocation = GetActorLocation();
    FVector topOfRoot = rootLocation + 200 * FVector(0, 0, 1);

    // LineTraceComponent: Trace child directly at child's location
    FHitResult hitResult;
    FVector start = topOfChild;
    FVector end = childLocation;
    FCollisionQueryParams params;
    if (_childMesh->LineTraceComponent(hitResult, start, end, params)) {
        DrawDebugLine(GetWorld(), start, end, FColor::Green, false, 0.0f, 0, 5.0f);
    } else {
        DrawDebugLine(GetWorld(), start, end, FColor::Red, false, 0.0f, 0, 5.0f);
    }

    // LineTraceComponent: Trace root at child's location (slightly to the right so you can see both traces)
    start = topOfChild + FVector(20, 0, 0);
    end = childLocation + FVector(20, 0, 0);
    if (_rootMesh->LineTraceComponent(hitResult, start, end, params)) {
        DrawDebugLine(GetWorld(), start, end, FColor::Green, false, 0.0f, 0, 5.0f);
    } else {
        DrawDebugLine(GetWorld(), start, end, FColor::Red, false, 0.0f, 0, 5.0f);
    }

    // LineTraceComponent: Trace root at root's location
    start = rootLocation;
    end = topOfRoot;
    if (_rootMesh->LineTraceComponent(hitResult, start, end, params)) {
        DrawDebugLine(GetWorld(), start, end, FColor::Green, false, 0.0f, 0, 5.0f);
    } else {
        DrawDebugLine(GetWorld(), start, end, FColor::Red, false, 0.0f, 0, 5.0f);
    }

    // LineTraceSingleByChannel: Try tracing with LineTraceSingleByChannel
    start = topOfChild + FVector(-20, 0, 0);
    end = childLocation + FVector(-20, 0, 0);
    FHitResult result;
    FCollisionResponseParams responseParams;
    if (GetWorld()->LineTraceSingleByChannel(result, start, end, ECC_WorldDynamic, params, responseParams)) {
        DrawDebugLine(GetWorld(), start, end, FColor::Green, false, 0.0f, 0, 5.0f);
    } else {
        DrawDebugLine(GetWorld(), start, end, FColor::Red, false, 0.0f, 0, 5.0f);
    }
}