Implementing Vehicle Wheel Sweeps for off-road game

I am working on an off-road racing game. Because Unreal uses the single raycast for suspension, the car does not respond well to driving over very bumpy terrain like piled logs.

I want to replace the raycast solution with PhysX’s sweep solution, which is designed just for this. I have modified my C++ files (especially PhysXVehicleManager) to perform sweeps with no errors. The problem is that my car’s wheels fall through the world without doing anything, like they do not detect any contact points which are recognized by the vehicle physics code.

Documentation by PhysX on sweeps: [Vehicles — NVIDIA PhysX SDK 3.4.0 Documentation

](Vehicles — NVIDIA PhysX SDK 3.4.0 Documentation)

To be specific, I expect to implement PxVehicleSuspensionSweeps
https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/apireference/files/group__vehicle.html#g3c9f3da897c9294c9164aa41b18150e3

There is a snippet provided by PhysX with an example of using sweeps. But their implementation is significantly different than Unreal’s. It does not have the integration into the VehicleMovement component that I’d like to preserve. Their code: PhysX-3.4/SnippetVehicleContactMod.cpp at 326e24039dd3f063afd211fad770e15bdd5ff4ea · NVIDIAGameWorks/PhysX-3.4 · GitHub

Any suggestions on how to make wheel sweep work in Unreal? Example code, working plugins, etc?

Here is my code so far from PhysXVehicleManager.cpp. I have also made small changes to other files to support this work.

[SPOILER]



// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.

#include "PhysXVehicleManager.h"
#include "UObject/UObjectIterator.h"
#include "TireConfig.h"

#include "PhysicalMaterials/PhysicalMaterial.h"
#include "Physics/PhysicsFiltering.h"
#include "PhysXPublic.h"
#include "Physics/PhysicsInterfaceCore.h"

DEFINE_LOG_CATEGORY(LogVehicles);

// --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
//Angle thresholds used to categorize contacts as suspension contacts or rigid body contacts.
#define POINT_REJECT_ANGLE PxPi/4.0f
#define NORMAL_REJECT_ANGLE PxPi/4.0f

#if WITH_PHYSX_VEHICLES

DECLARE_STATS_GROUP(TEXT("PhysXVehicleManager"), STATGROUP_PhysXVehicleManager, STATGROUP_Advanced);

// --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
DECLARE_CYCLE_STAT(TEXT("PxVehicleSuspensionSweeps"), STAT_PhysXVehicleManager_PxVehicleSuspensionSweeps, STATGROUP_PhysXVehicleManager);

DECLARE_CYCLE_STAT(TEXT("PxVehicleSuspensionRaycasts"), STAT_PhysXVehicleManager_PxVehicleSuspensionRaycasts, STATGROUP_PhysXVehicleManager);

DECLARE_CYCLE_STAT(TEXT("PxUpdateVehicles"), STAT_PhysXVehicleManager_PxUpdateVehicles, STATGROUP_PhysXVehicleManager);
DECLARE_CYCLE_STAT(TEXT("UpdateTireFrictionTable"), STAT_PhysXVehicleManager_UpdateTireFrictionTable, STATGROUP_PhysXVehicleManager);
DECLARE_CYCLE_STAT(TEXT("TickVehicles"), STAT_PhysXVehicleManager_TickVehicles, STATGROUP_PhysXVehicleManager);
DECLARE_CYCLE_STAT(TEXT("VehicleManager Update"), STAT_PhysXVehicleManager_Update, STATGROUP_PhysXVehicleManager);
DECLARE_CYCLE_STAT(TEXT("Pretick Vehicles"), STAT_PhysXVehicleManager_PretickVehicles, STATGROUP_Physics);

bool FPhysXVehicleManager::bUpdateTireFrictionTable = false;
PxVehicleDrivableSurfaceToTireFrictionPairs* FPhysXVehicleManager::SurfaceTirePairs = NULL;
TMap<FPhysScene*, FPhysXVehicleManager*> FPhysXVehicleManager::SceneToVehicleManagerMap;
uint32 FPhysXVehicleManager::VehicleSetupTag = 0;

/**
 * prefilter shader for suspension raycasts
 */
static PxQueryHitType::Enum WheelRaycastPreFilter(    
    PxFilterData SuspensionData, 
    PxFilterData HitData,
    const void* constantBlock, PxU32 constantBlockSize,
    PxHitFlags& filterFlags)
{
    // SuspensionData is the vehicle suspension raycast.
    // HitData is the shape potentially hit by the raycast.

    // don't collide with owner chassis
    if ( SuspensionData.word0 == HitData.word0 )
    {
        return PxQueryHitType::eNONE;
    }

    PxU32 ShapeFlags = SuspensionData.word3 & 0xFFFFFF;
    PxU32 QuerierFlags = HitData.word3 & 0xFFFFFF;
    PxU32 CommonFlags = ShapeFlags & QuerierFlags;

    // Check complexity matches
    if (!(CommonFlags & EPDF_SimpleCollision) && !(CommonFlags & EPDF_ComplexCollision))
    {
        return PxQueryHitType::eNONE;
    }

    // collision channels filter
    ECollisionChannel SuspensionChannel = GetCollisionChannel(SuspensionData.word3);

    if ( ECC_TO_BITFIELD(SuspensionChannel) & HitData.word1)
    {
        // debug what object we hit
        if ( false )
        {
            for ( FObjectIterator It; It; ++It )
            {
                if ( It->GetUniqueID() == HitData.word0 )
                {
                    UObject* HitObj = *It;
                    FString HitObjName = HitObj->GetName();
                    break;
                }
            }
        }

        return PxQueryHitType::eBLOCK;
    }

    return PxQueryHitType::eNONE;
}

// --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
/*
FPhysXVehicleManager::FPhysXVehicleManager(FPhysScene* PhysScene)
    : WheelRaycastBatchQuery(NULL)
    */

FPhysXVehicleManager::FPhysXVehicleManager(FPhysScene* PhysScene)
    : WheelSweepBatchQuery(NULL)


#if PX_DEBUG_VEHICLE_ON
    , TelemetryData4W(NULL)
    , TelemetryVehicle(NULL)
#endif
{
    // Save pointer to PhysX scene
    Scene = PhysScene->GetPxScene();

    // Set up delegates
    OnPhysScenePreTickHandle = PhysScene->OnPhysScenePreTick.AddRaw(this, &FPhysXVehicleManager::PreTick);
    OnPhysSceneStepHandle = PhysScene->OnPhysSceneStep.AddRaw(this, &FPhysXVehicleManager::Update);

    // Add to map
    FPhysXVehicleManager::SceneToVehicleManagerMap.Add(PhysScene, this);


    // Set the correct basis vectors with Z up, X forward. It's very IMPORTANT to set the Ackermann axle separation and frontWidth, rearWidth accordingly
    PxVehicleSetBasisVectors( PxVec3(0,0,1), PxVec3(1,0,0) );
}

void FPhysXVehicleManager::DetachFromPhysScene(FPhysScene* PhysScene)
{
    PhysScene->OnPhysScenePreTick.Remove(OnPhysScenePreTickHandle);
    PhysScene->OnPhysSceneStep.Remove(OnPhysSceneStepHandle);

    FPhysXVehicleManager::SceneToVehicleManagerMap.Remove(PhysScene);
}

FPhysXVehicleManager::~FPhysXVehicleManager()
{
#if PX_DEBUG_VEHICLE_ON
    if(TelemetryData4W)
    {
        TelemetryData4W->free();
        TelemetryData4W = NULL;
    }

    TelemetryVehicle = NULL;
#endif

    // Remove the N-wheeled vehicles.
    while( Vehicles.Num() > 0 )
    {
        RemoveVehicle( Vehicles.Last() );
    }

    // Release batch query data
    // --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
    /*
    if ( WheelRaycastBatchQuery )
    {
        WheelRaycastBatchQuery->release();
        WheelRaycastBatchQuery = NULL;
    }
    */
    // --<

    if (WheelSweepBatchQuery)
    {
        WheelSweepBatchQuery->release();
        WheelSweepBatchQuery = NULL;
    }

    // Release the  friction values used for combinations of tire type and surface type.
    //if ( SurfaceTirePairs )
    //{
    //    SurfaceTirePairs->release();
    //    SurfaceTirePairs = NULL;
    //}
}

FPhysXVehicleManager* FPhysXVehicleManager::GetVehicleManagerFromScene(FPhysScene* PhysScene)
{
    FPhysXVehicleManager* Manager = nullptr;
    FPhysXVehicleManager** ManagerPtr = SceneToVehicleManagerMap.Find(PhysScene);
    if (ManagerPtr != nullptr)
    {
        Manager = *ManagerPtr;
    }
    return Manager;
}

static UTireConfig* DefaultTireConfig = nullptr;

UTireConfig* FPhysXVehicleManager::GetDefaultTireConfig()
{
    if (DefaultTireConfig == nullptr)
    {
        DefaultTireConfig = NewObject<UTireConfig>();
        DefaultTireConfig->AddToRoot(); // prevent GC
    }

    return DefaultTireConfig;
}

void FPhysXVehicleManager::UpdateTireFrictionTable()
{
    bUpdateTireFrictionTable = true;
}

void FPhysXVehicleManager::UpdateTireFrictionTableInternal()
{
    const PxU32 MAX_NUM_MATERIALS = 128;

    // There are tire types and then there are drivable surface types.
    // PhysX supports physical materials that share a drivable surface type,
    // but we just create a drivable surface type for every type of physical material
    PxMaterial*                            AllPhysicsMaterials[MAX_NUM_MATERIALS];
    PxVehicleDrivableSurfaceType        DrivableSurfaceTypes[MAX_NUM_MATERIALS];

    // Gather all the physical materials
    uint32 NumMaterials = GPhysXSDK->getMaterials(AllPhysicsMaterials, MAX_NUM_MATERIALS);

    uint32 NumTireConfigs = UTireConfig::AllTireConfigs.Num();

    for ( uint32 m = 0; m < NumMaterials; ++m )
    {
        // Set up the drivable surface type that will be used for the new material.
        DrivableSurfaceTypes[m].mType = m;
    }

    // Release the previous SurfaceTirePairs, if any
    if ( SurfaceTirePairs )
    {
        SurfaceTirePairs->release();
        SurfaceTirePairs = NULL;
    }

    // Set up the friction values arising from combinations of tire type and surface type.
    SurfaceTirePairs = PxVehicleDrivableSurfaceToTireFrictionPairs::allocate( NumTireConfigs, NumMaterials );
    SurfaceTirePairs->setup(NumTireConfigs, NumMaterials, (const PxMaterial**)AllPhysicsMaterials, DrivableSurfaceTypes );

    // Iterate over each physical material
    for ( uint32 m = 0; m < NumMaterials; ++m )
    {
        UPhysicalMaterial* PhysMat = FPhysxUserData::Get<UPhysicalMaterial>(AllPhysicsMaterials[m]->userData);
        if (PhysMat != nullptr)
        {
            // Iterate over each tire config
            for (uint32 t = 0; t < NumTireConfigs; ++t)
        {
                UTireConfig* TireConfig = UTireConfig::AllTireConfigs[t].Get();
                if (TireConfig != nullptr)
            {
                    float TireFriction = TireConfig->GetTireFriction(PhysMat);
                    SurfaceTirePairs->setTypePairFriction(m, t, TireFriction);
            }
            }
        }
    }
}

void FPhysXVehicleManager::SetUpBatchedSceneQuery()
{
    int32 NumWheels = 0;

    for ( int32 v = PVehicles.Num() - 1; v >= 0; --v )
    {
        NumWheels += PVehicles[v]->mWheelsSimData.getNbWheels();
    }

    // --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
    /*
    if ( NumWheels > WheelQueryResults.Num())
    {
        WheelQueryResults.AddZeroed( NumWheels - WheelQueryResults.Num() );
        WheelHitResults.AddZeroed( NumWheels - WheelHitResults.Num() );

        check( WheelHitResults.Num() == WheelQueryResults.Num() );

        if ( WheelRaycastBatchQuery )
        {
            WheelRaycastBatchQuery->release();
            WheelRaycastBatchQuery = NULL;
        }

        PxBatchQueryDesc SqDesc(NumWheels, 0, 0);
        SqDesc.queryMemory.userRaycastResultBuffer = WheelQueryResults.GetData();
        SqDesc.queryMemory.userRaycastTouchBuffer = WheelHitResults.GetData();
        SqDesc.queryMemory.raycastTouchBufferSize = WheelHitResults.Num();
        SqDesc.preFilterShader = WheelRaycastPreFilter;

        WheelRaycastBatchQuery = Scene->createBatchQuery( SqDesc );
    }
    */
    // --<

    if (NumWheels > WheelSweepQueryResults.Num())
    {
        WheelSweepQueryResults.AddZeroed(NumWheels*8 - WheelSweepQueryResults.Num()*8);
        WheelSweepResults.AddZeroed(NumWheels*8 - WheelSweepResults.Num()*8);

        check(WheelSweepResults.Num() == WheelSweepQueryResults.Num());

        if (WheelSweepBatchQuery)
        {
            WheelSweepBatchQuery->release();
            WheelSweepBatchQuery = NULL;
        }

        PxBatchQueryDesc SqDesc(NumWheels, 0, 0);
        SqDesc.queryMemory.userSweepResultBuffer = WheelSweepQueryResults.GetData();
        SqDesc.queryMemory.userSweepTouchBuffer = WheelSweepResults.GetData();
        SqDesc.queryMemory.sweepTouchBufferSize = WheelSweepResults.Num();
        SqDesc.preFilterShader = WheelRaycastPreFilter;

        PxVehicleSetSweepHitRejectionAngles(POINT_REJECT_ANGLE, NORMAL_REJECT_ANGLE);

        WheelSweepBatchQuery = Scene->createBatchQuery(SqDesc);
    }
}

void FPhysXVehicleManager::AddVehicle( TWeakObjectPtr<UWheeledVehicleMovementComponent> Vehicle )
{
    check(Vehicle != NULL);
    check(Vehicle->PVehicle);

    Vehicles.Add( Vehicle );
    PVehicles.Add( Vehicle->PVehicle );

    // init wheels' states
    int32 NewIndex = PVehiclesWheelsStates.AddZeroed();
    PxU32 NumWheels = Vehicle->PVehicle->mWheelsSimData.getNbWheels();
    PVehiclesWheelsStates[NewIndex].nbWheelQueryResults = NumWheels;
    PVehiclesWheelsStates[NewIndex].wheelQueryResults = new PxWheelQueryResult[NumWheels];  

    SetUpBatchedSceneQuery();
}

void FPhysXVehicleManager::RemoveVehicle( TWeakObjectPtr<UWheeledVehicleMovementComponent> Vehicle )
{
    check(Vehicle != NULL);
    check(Vehicle->PVehicle);

    PxVehicleWheels* PVehicle = Vehicle->PVehicle;

    int32 RemovedIndex = Vehicles.Find(Vehicle);

    Vehicles.Remove( Vehicle );
    PVehicles.Remove( PVehicle );

    delete] PVehiclesWheelsStates[RemovedIndex].wheelQueryResults;
    PVehiclesWheelsStates.RemoveAt(RemovedIndex); // LOC_MOD double check this
    //PVehiclesWheelsStates.Remove(PVehiclesWheelsStates[RemovedIndex]);

    if ( PVehicle == TelemetryVehicle )
    {
        TelemetryVehicle = NULL;
    }

    switch( PVehicle->getVehicleType() )
    {
    case PxVehicleTypes::eDRIVE4W:
        ((PxVehicleDrive4W*)PVehicle)->free();
        break;
    case PxVehicleTypes::eDRIVETANK:
        ((PxVehicleDriveTank*)PVehicle)->free();
        break;
    case PxVehicleTypes::eDRIVENW:
        ((PxVehicleDriveNW*)PVehicle)->free();
        break;
    case PxVehicleTypes::eNODRIVE:
        ((PxVehicleNoDrive*)PVehicle)->free();
        break;
    default:
        checkf( 0, TEXT("Unsupported vehicle type"));
        break;
    }
}

void FPhysXVehicleManager::Update(FPhysScene* PhysScene, float DeltaTime)
{
    SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_Update);

    // Only support vehicles in sync scene
    if (Vehicles.Num() == 0 )
    {
        return;
    }

    if ( bUpdateTireFrictionTable )
    {
        SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_UpdateTireFrictionTable);
        bUpdateTireFrictionTable = false;
        UpdateTireFrictionTableInternal();
    }

    // --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
    // Suspension sweeps instead of raycasts
    // --<
    {
        SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_PxVehicleSuspensionSweeps);
        SCOPED_SCENE_READ_LOCK(Scene);

        // --> 2019-12-26 NathanielJ P172-334 Whole wheel surface interacts realistically with terrain
        // PxVehicleSuspensionRaycasts( WheelRaycastBatchQuery, PVehicles.Num(), PVehicles.GetData(), WheelQueryResults.Num(), WheelQueryResults.GetData() );
        // --<
        PxVehicleSuspensionSweeps( WheelSweepBatchQuery, PVehicles.Num(), PVehicles.GetData(), WheelSweepQueryResults.Num(), WheelSweepQueryResults.GetData(), 16 , NULL, 1.0f, 1.0f);
    }


    // Tick vehicles
    {
        SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_TickVehicles);
        for (int32 i = Vehicles.Num() - 1; i >= 0; --i)
        {
            Vehicles*->TickVehicle(DeltaTime);
        }
    }

#if PX_DEBUG_VEHICLE_ON

    if ( TelemetryVehicle != NULL )
    {
        UpdateVehiclesWithTelemetry( DeltaTime );
    }
    else
    {
        UpdateVehicles( DeltaTime );
    }

#else

    UpdateVehicles( DeltaTime );

#endif //PX_DEBUG_VEHICLE_ON
}

void FPhysXVehicleManager::PreTick(FPhysScene* PhysScene, float DeltaTime)
{
    SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_PretickVehicles);

    for (int32 i = 0; i < Vehicles.Num(); ++i)
    {
        Vehicles*->PreTick(DeltaTime);
    }
}


void FPhysXVehicleManager::UpdateVehicles( float DeltaTime )
{
    SCOPE_CYCLE_COUNTER(STAT_PhysXVehicleManager_PxUpdateVehicles);
    SCOPED_SCENE_WRITE_LOCK(Scene);
    PxVehicleUpdates( DeltaTime, GetSceneGravity_AssumesLocked(), *SurfaceTirePairs, PVehicles.Num(), PVehicles.GetData(), PVehiclesWheelsStates.GetData());
}

PxVec3 FPhysXVehicleManager::GetSceneGravity_AssumesLocked()
{
    return Scene->getGravity();
}

void FPhysXVehicleManager::SetRecordTelemetry( TWeakObjectPtr<UWheeledVehicleMovementComponent> Vehicle, bool bRecord )
{
#if PX_DEBUG_VEHICLE_ON

    if ( Vehicle != NULL && Vehicle->PVehicle != NULL )
    {
        PxVehicleWheels* PVehicle = Vehicle->PVehicle;

        if ( bRecord )
        {
            int32 VehicleIndex = Vehicles.Find( Vehicle );

            if ( VehicleIndex != INDEX_NONE )
            {
                // Make sure telemetry is setup
                SetupTelemetryData();

                TelemetryVehicle = PVehicle;

                if ( VehicleIndex != 0 )
                {
                    Vehicles.Swap( 0, VehicleIndex );
                    PVehicles.Swap( 0, VehicleIndex );
                    PVehiclesWheelsStates.Swap( 0, VehicleIndex );
                }
            }
        }
        else
        {
            if ( PVehicle == TelemetryVehicle )
            {
                TelemetryVehicle = NULL;
            }
        }
    }

#endif
}

#if PX_DEBUG_VEHICLE_ON

void FPhysXVehicleManager::SetupTelemetryData()
{
    // set up telemetry for 4 wheels
    if(TelemetryData4W == NULL)
    {
        SCOPED_SCENE_WRITE_LOCK(Scene);

        float Empty] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };

        TelemetryData4W = PxVehicleTelemetryData::allocate(4);
        TelemetryData4W->setup(1.0f, 1.0f, 0.0f, 0.0f, Empty, Empty, PxVec3(0,0,0), PxVec3(0,0,0), PxVec3(0,0,0));
    }
}

void FPhysXVehicleManager::UpdateVehiclesWithTelemetry( float DeltaTime )
{
    check(TelemetryVehicle);
    check(PVehicles.Find(TelemetryVehicle) == 0);

    SCOPED_SCENE_WRITE_LOCK(Scene);
    if ( PxVehicleTelemetryData* TelemetryData = GetTelemetryData_AssumesLocked() )
    {
        PxVehicleUpdateSingleVehicleAndStoreTelemetryData( DeltaTime, GetSceneGravity_AssumesLocked(), *SurfaceTirePairs, TelemetryVehicle, PVehiclesWheelsStates.GetData(), *TelemetryData );

        if ( PVehicles.Num() > 1 )
        {
            PxVehicleUpdates( DeltaTime, GetSceneGravity_AssumesLocked(), *SurfaceTirePairs, PVehicles.Num() - 1, &PVehicles[1], &PVehiclesWheelsStates[1] );
        }
    }
    else
    {
        UE_LOG( LogPhysics, Warning, TEXT("Cannot record telemetry for vehicle, it does not have 4 wheels") );

        PxVehicleUpdates( DeltaTime, GetSceneGravity_AssumesLocked(), *SurfaceTirePairs, PVehicles.Num(), PVehicles.GetData(), PVehiclesWheelsStates.GetData() );
    }
}

PxVehicleTelemetryData* FPhysXVehicleManager::GetTelemetryData_AssumesLocked()
{
    if ( TelemetryVehicle )
    {
        if ( TelemetryVehicle->mWheelsSimData.getNbWheels() == 4 )
        {
            return TelemetryData4W;
        }
    }

    return nullptr;
}

#endif //PX_DEBUG_VEHICLE_ON

PxWheelQueryResult* FPhysXVehicleManager::GetWheelsStates_AssumesLocked(TWeakObjectPtr<const UWheeledVehicleMovementComponent> Vehicle)
{
    int32 Index = Vehicles.IndexOfByKey(Vehicle);

    if(Index != INDEX_NONE)
    {
        return PVehiclesWheelsStates[Index].wheelQueryResults;
    }
    else
    {
        return nullptr;
    }
}

#endif // WITH_PHYSX


[/SPOILER]

I had a similar issue. Try switching to the checked build of the PhysX libraries which validates more parameters and see if your getting a complaint on the console about invalid geometry?

In your TestEditorTarget.cs:


public class WheeledVehicleTestEditorTarget : TargetRules
{
    public WheeledVehicleTestEditorTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Editor;
        DefaultBuildSettings = BuildSettingsVersion.V2;
        ExtraModuleNames.Add("WheeledVehicleTest");

        bUseCheckedPhysXLibraries = true;  // <============ ADD THIS LINE
    }
}

My issue was because the default code is setting an “Engine/EngineMeshes/Cylinder” as the wheel collision mesh object. While this appears to be valid It is a “triangle” mesh. This trips up the PhysX code [PxVehicleWheels4SuspensionSweeps in PxVehicleUpdate] which is expecting a “Convex Mesh/Capsule/Sphere” to be used for the swept shape and treats the triangle mesh incorrectly as a sphere!. I copied the Cylinder into my project and generated a Convex collision mesh for it, substituted that mesh for the wheel collision mesh, and my wheels then rested on the ground. The vehicle became driveable again!

Hope this helps!

I have the same problem. I also make small changes to PhysXVehicleManager.cpp just like UnrealNateJ does, but when begin playing, wheels have no collision against the ground.
I have generated the “Convex collision” for “Engine/EngineMeshes/Cylinder” as follow picture shows. Green lines show the convex collision.
Capture.JPG
I not sure whether the change of “PhysXVehicleManager.cpp” is correct and whether I must override code somewhere else.
Can you show some more details of how to change in “PhysXVehicleManager.cpp” or other .cpp file? Thank you so much.@
wuzzy

PS:Here is another topic of car physics.
https://forums.unrealengine.com/unreal-engine/feedback-for-epic/19243-horrible-car-physics
And some people discussed this issue at page 8 #113

Great tip! I was very bothered that PhysX wasn’t giving any useful info as to why the system wasn’t working. Where did you find out that you could get this more descriptive output?

I can give your technique a shot. But like yazhangmu requested…

I would also appreciate detail if you can post detail, Wuzzy! You seem to know a lot on the topic!

Speaking of which,

I did read through some of this thread, and did any of these physics issues end up resolved? I know my test vehicle still tends to launch in the air and behave strangely. I’m open to any tips or best practices for doing vehicles in Unreal today.

I see @[URL=“https://forums.unrealengine.com/member/22522-mike-skolones”]Mike.Skolones](https://forums.unrealengine.com/member/22522-mike-skolones) was trying to improve this, but he stopped posting on the forums in 2016.

@wuzzy I do not have that .cs file. I have the cs file for the project (like “VehicleGame”) the editor (UE4editor), and others. Which are you referring to?

Adding it to the project target file has not changed what I see in the Output window.

I checked and the cylinder in the UE4 editor does already have a complex convex collision mesh. I tried copying the cylinder static mesh and adding a simplified convex collision mesh. No change. With my modified code running, like in my original post, the car still falls through the ground.

Would appreciate more ideas!

To help others who may be in a similar situation, I did figure out some things.

  1. Where to put “bUseCheckedPhysXLibraries = true;” is in the file with the your project name + EditorTarget.cs. After rebuilding the engine with this, I was getting helpful error information in the Output window.
  2. It turned out that my issue was that I was not correctly allocating memory for the sweeps. When setting up the batched scene query for sweeps, use:


// Note that we need to use the second input for setting the sweep memory
PxBatchQueryDesc SqDesc(0, NumWheels, 0); // PxBatchQueryDesc (PxU32 maxRaycastsPerExecute, PxU32 maxSweepsPerExecute, PxU32 maxOverlapsPerExecute)


Reference: https://documentation.help/NVIDIA-Ph…QueryDesc.html

By the way, I found that using multiple vehicles would still cause the out of memory error. Hand-coding extra sweeps into the memory solved the issue, but it should have correctly calculated the sweep count from NumWheels. If anyone knows how to fix this, I would appreciate the advice.

This got the car driving, but it would tend to have its wheels fall through the world when around complex geometry. So, I added a “post” filter shader:



PxQueryHitType::Enum WheelSceneQueryPostFilterBlocking
(PxFilterData filterData0, PxFilterData filterData1,
    const void* constantBlock, PxU32 constantBlockSize,
    const PxQueryHit& hit)
{
    PX_UNUSED(filterData0);
    PX_UNUSED(filterData1);
    PX_UNUSED(constantBlock);
    PX_UNUSED(constantBlockSize);
    if ((static_cast<const PxSweepHit&>(hit)).hadInitialOverlap())
        return PxQueryHitType::eNONE;
    return PxQueryHitType::eBLOCK;
}


It is not a perfect solution but causes the wheel suspension to act properly in the large majority of off-road situations.

Finally, I want to note that @wuzzy was right about using a generated wheel collider. No other collider types worked. However, I did not end up going with this method because it seems to sweep from the (limited) edges of the collider, causing jerky suspension movement at low speeds. Instead, I found the setting “UsePhysAssetShape” caused very smooth motion at all speeds. Still not perfect, but it looked much better.

Hope this helps @yazhangmu and others also working on this. Without feedback on the forum I would not have gotten this far.

With some final work, I think I can mark this as solved.

Looking in WheeledVehicleMovementComponent.cpp, I saw that checking UsePhysAssetShape would cause large collision spheres to be generated for each tire. Ultimately…



if(!PWheelShape)
{
    PWheelShape = GPhysXSDK->createShape(PxSphereGeometry(Wheel->ShapeRadius), *WheelMaterial, /*bIsExclusive=*/true);
    PWheelShape->setLocalPose(PLocalPose);
    PVehicleActor->attachShape(*PWheelShape);
    PWheelShape->release();
}


These spheres were invisible in the editor and causing strange, unwanted behavior when driving in tight spaces.

However, it seems this collision surface was needed to get smooth driving at low speeds over complex terrain. I thought that perhaps I needed to import cylinder collision into PhysX, but all the online documentation said that a convex collider would work just as well. But in my testing, the convex collider was much worse.

I believe the real issue was that PhysX was automatically shrinking the wheel’s collision mesh to be smaller than the swept distance.



else if(Wheel->CollisionMesh && Wheel->CollisionMesh->BodySetup)
 {
     WheelBodySetup = Wheel->CollisionMesh->BodySetup;

    FBoxSphereBounds MeshBounds = Wheel->CollisionMesh->GetBounds();
    if(Wheel->bAutoAdjustCollisionSize)
    {
        MeshScaleV.X = Wheel->ShapeRadius / MeshBounds.BoxExtent.X;
        MeshScaleV.Y = Wheel->ShapeWidth / MeshBounds.BoxExtent.Y;
        MeshScaleV.Z = Wheel->ShapeRadius / MeshBounds.BoxExtent.Z;
    }
}


I tested to see what would happen if the collision mesh was slightly larger than the wheel radius (also used by swept area). I turned off “Auto Adjust Collision Size” and scaled up the convex collision on the wheel’s Collision Mesh. On the Epic buggy, this is quite a bit larger than the base cylinder.

LargerWheelCollider.JPG
*Not the final size. I ended up playing for a while before I got a stable combination of collider scale and wheel settings.

With the collider larger than the wheel radius setting, now the car rolls smoothly over almost any surface.

Sweep_LargerConvexCollider_Snip.jpg