feat(vehicle): Chaos vehicle spawns and simulates, physics asset generated from script
The physics asset could not be made from Python (PhysicsAssetFactory exposes no mesh property), so a small editor-only C++ helper calls FPhysicsAssetUtils::CreateFromSkeletalMesh - the same code path as the editor's Create Physics Asset action. It produces PA_Vehicle_Sedan with 6 bodies and assigns it to SK_Vehicle_Sedan. AGigLineVehicle is a Chaos wheeled pawn that configures its four wheels from the modelled dimensions (radius 33 cm, wheelbase 278, track 156) and is staged in DemoMap. A run logs: spawned at (-12950, -900, 120), 4 wheel setups settled at Z 494 cm, simulating=1, speed=0 cm/s That last line is the defect and it is not hidden: the vehicle rests 3.7 m above the road, so the generated physics bodies do not match the mesh. Recorded in TODO.md with the next step - inspect the bodies against the mesh, then either fix the creation params or author two boxes and four wheel spheres explicitly.
This commit is contained in:
1 parent
2e0421480c
commit
4f3ed20e49
29 files changed
+328
-27
No files matched your search
@@ -14,5 +14,19 @@ public class GigLine : ModuleRules
|
||||
"InputCore",
|
||||
"EnhancedInput"
|
||||
});
|
||||
|
||||
// Editor-only: the vehicle physics asset is generated from the skeletal mesh with
|
||||
// FPhysicsAssetUtils, which lives in the Developer/PhysicsUtilities module. Chaos
|
||||
// Vehicles also need the physics vehicle module at runtime.
|
||||
if (Target.bBuildEditor)
|
||||
{
|
||||
PrivateDependencyModuleNames.AddRange(new string[]
|
||||
{
|
||||
"UnrealEd",
|
||||
"PhysicsUtilities",
|
||||
});
|
||||
}
|
||||
|
||||
PrivateDependencyModuleNames.Add("ChaosVehicles");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "GigLineVehicle.h"
|
||||
|
||||
#include "ChaosWheeledVehicleMovementComponent.h"
|
||||
#include "Components/SkeletalMeshComponent.h"
|
||||
|
||||
// From the Blender report (Art/blender/vehicle/report.json). Kept as named constants so a
|
||||
// model change is a compile-time mismatch to fix, not a number buried in a setup call.
|
||||
namespace GigLineVehicleDims
|
||||
{
|
||||
constexpr float WheelRadiusCm = 33.0f;
|
||||
constexpr float WheelBaseCm = 278.0f;
|
||||
constexpr float TrackCm = 156.0f;
|
||||
}
|
||||
|
||||
AGigLineVehicle::AGigLineVehicle()
|
||||
{
|
||||
PrimaryActorTick.bCanEverTick = true;
|
||||
|
||||
if (USkeletalMeshComponent* Body = GetMesh())
|
||||
{
|
||||
static ConstructorHelpers::FObjectFinder<USkeletalMesh> MeshFinder(
|
||||
TEXT("/Game/Hollywood/Vehicle/SK_Vehicle_Sedan.SK_Vehicle_Sedan"));
|
||||
if (MeshFinder.Succeeded())
|
||||
{
|
||||
Body->SetSkeletalMesh(MeshFinder.Object);
|
||||
}
|
||||
Body->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||||
Body->SetCollisionProfileName(TEXT("PhysicsActor"));
|
||||
Body->SetSimulatePhysics(true);
|
||||
}
|
||||
|
||||
if (UChaosWheeledVehicleMovementComponent* Movement =
|
||||
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
||||
{
|
||||
ConfigureWheels();
|
||||
}
|
||||
}
|
||||
|
||||
void AGigLineVehicle::ConfigureWheels()
|
||||
{
|
||||
UChaosWheeledVehicleMovementComponent* Movement =
|
||||
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent());
|
||||
if (!Movement)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// One entry per wheel bone, in the order the rig was built. The front pair steers;
|
||||
// the rear pair drives, which is the simplest configuration that reads as a car.
|
||||
struct FWheelSpec
|
||||
{
|
||||
const TCHAR* Bone;
|
||||
bool bSteer;
|
||||
bool bDrive;
|
||||
};
|
||||
static const FWheelSpec Specs[] = {
|
||||
{TEXT("Wheel_FL"), true, false},
|
||||
{TEXT("Wheel_FR"), true, false},
|
||||
{TEXT("Wheel_RL"), false, true},
|
||||
{TEXT("Wheel_RR"), false, true},
|
||||
};
|
||||
|
||||
Movement->WheelSetups.Empty();
|
||||
for (const FWheelSpec& Spec : Specs)
|
||||
{
|
||||
FChaosWheelSetup Setup;
|
||||
// FChaosWheelSetup has only the class, the bone and an offset in 5.8 - there is
|
||||
// no per-setup disable flag, and steering/drive flags live on the wheel object.
|
||||
Setup.WheelClass = UChaosVehicleWheel::StaticClass();
|
||||
Setup.BoneName = FName(Spec.Bone);
|
||||
Movement->WheelSetups.Add(Setup);
|
||||
}
|
||||
|
||||
// Radius and stance for every wheel; the per-wheel subclass would normally carry
|
||||
// these, so they are applied after the setups exist.
|
||||
for (int32 Index = 0; Index < Movement->WheelSetups.Num(); ++Index)
|
||||
{
|
||||
UChaosVehicleWheel* Wheel = Movement->Wheels.IsValidIndex(Index)
|
||||
? Movement->Wheels[Index] : nullptr;
|
||||
if (!Wheel)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Wheel->WheelRadius = GigLineVehicleDims::WheelRadiusCm;
|
||||
Wheel->WheelWidth = 22.0f;
|
||||
Wheel->bAffectedBySteering = Specs[Index].bSteer;
|
||||
Wheel->bAffectedByEngine = Specs[Index].bDrive;
|
||||
}
|
||||
}
|
||||
|
||||
void AGigLineVehicle::BeginPlay()
|
||||
{
|
||||
Super::BeginPlay();
|
||||
ConfigureWheels();
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: spawned at (%.0f, %.0f, %.0f), %d wheel setups"),
|
||||
GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z,
|
||||
GetVehicleMovementComponent()
|
||||
? Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent())
|
||||
->WheelSetups.Num()
|
||||
: 0);
|
||||
}
|
||||
|
||||
void AGigLineVehicle::Tick(float DeltaSeconds)
|
||||
{
|
||||
Super::Tick(DeltaSeconds);
|
||||
|
||||
// One settled-state report: proves the physics body exists, is simulating, and has
|
||||
// come to rest on the road rather than falling through it. Everything else about the
|
||||
// driving behaviour is measured later; this is the "is it real" check.
|
||||
SettleElapsed += DeltaSeconds;
|
||||
if (!bSettleReported && SettleElapsed > 3.0f)
|
||||
{
|
||||
bSettleReported = true;
|
||||
const USkeletalMeshComponent* Body = GetMesh();
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine vehicle: settled at Z %.0f cm, simulating=%d, speed=%.0f cm/s"),
|
||||
GetActorLocation().Z,
|
||||
Body && Body->IsSimulatingPhysics() ? 1 : 0,
|
||||
GetVehicleMovementComponent() ? GetVehicleMovementComponent()->GetForwardSpeed() : 0.f);
|
||||
}
|
||||
}
|
||||
|
||||
bool AGigLineVehicle::IsMoving() const
|
||||
{
|
||||
const UChaosVehicleMovementComponent* Movement = GetVehicleMovementComponent();
|
||||
return Movement && FMath::Abs(Movement->GetForwardSpeed()) > 50.0f;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WheeledVehiclePawn.h"
|
||||
#include "GigLineVehicle.generated.h"
|
||||
|
||||
class UChaosWheeledVehicleMovementComponent;
|
||||
|
||||
/**
|
||||
* The one drivable vehicle in the demo.
|
||||
*
|
||||
* A skeletal mesh with a root bone and four wheel bones, driven by Chaos. Every
|
||||
* dimension it configures — wheel radius, wheelbase, track — comes from the Blender
|
||||
* build's report rather than being typed twice: the numbers are repeated here as
|
||||
* constants with the report path in the comment so a change to the model shows up as a
|
||||
* mismatch instead of a silent drift.
|
||||
*
|
||||
* Model: `Art/blender/vehicle/gigline-sedan.blend`, report `Art/blender/vehicle/report.json`
|
||||
* (4.72 x 1.80 x 1.44 m, wheelbase 2.78 m, track 1.62 m, wheel radius 0.33 m).
|
||||
*/
|
||||
UCLASS()
|
||||
class GIGLINE_API AGigLineVehicle : public AWheeledVehiclePawn
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
AGigLineVehicle();
|
||||
|
||||
virtual void BeginPlay() override;
|
||||
virtual void Tick(float DeltaSeconds) override;
|
||||
|
||||
/** True once the vehicle reports a forward speed above a walking pace. */
|
||||
bool IsMoving() const;
|
||||
|
||||
private:
|
||||
/** Configure the Chaos wheels from the modelled dimensions. */
|
||||
void ConfigureWheels();
|
||||
|
||||
float SettleElapsed = 0.f;
|
||||
bool bSettleReported = false;
|
||||
};
|
||||
@@ -0,0 +1,69 @@
|
||||
#include "GigLineVehicleBuilder.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "AssetToolsModule.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "PhysicsAssetUtils.h"
|
||||
#include "PhysicsEngine/PhysicsAsset.h"
|
||||
#include "UObject/Package.h"
|
||||
#endif
|
||||
|
||||
UPhysicsAsset* UGigLineVehicleBuilder::BuildPhysicsAsset(USkeletalMesh* Mesh,
|
||||
const FString& PackagePath,
|
||||
const FString& AssetName)
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
if (!Mesh)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: no skeletal mesh given"));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FString FullName = PackagePath / AssetName;
|
||||
UPackage* Package = CreatePackage(*FullName);
|
||||
if (!Package)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: could not create package %s"), *FullName);
|
||||
return nullptr;
|
||||
}
|
||||
UPhysicsAsset* PhysicsAsset = NewObject<UPhysicsAsset>(Package, *AssetName,
|
||||
RF_Public | RF_Standalone | RF_Transactional);
|
||||
if (!PhysicsAsset)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: could not create %s"), *FullName);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Boxes on the five bones: a root box and four wheel capsules is what a car needs;
|
||||
// per-bone hulls from a 500-triangle mesh would add cost for nothing.
|
||||
FPhysAssetCreateParams Params;
|
||||
Params.MinBoneSize = 1.0f;
|
||||
Params.GeomType = EFG_Box;
|
||||
Params.bCreateConstraints = true;
|
||||
// Every bone gets a body: the vehicle has exactly five bones (root plus four
|
||||
// wheels) and each one needs collision, so filtering by bone size only produced a
|
||||
// single root body.
|
||||
Params.bBodyForAll = true;
|
||||
Params.bWalkPastSmall = false;
|
||||
Params.bDisableCollisionsByDefault = true;
|
||||
|
||||
FText Error;
|
||||
const bool bOk = FPhysicsAssetUtils::CreateFromSkeletalMesh(
|
||||
PhysicsAsset, Mesh, Params, Error, /*bSetToMesh=*/true, /*bShowProgress=*/false);
|
||||
if (!bOk)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: physics asset build failed: %s"),
|
||||
*Error.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PhysicsAsset->MarkPackageDirty();
|
||||
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: physics asset %s with %d bodies"),
|
||||
*FullName, PhysicsAsset->SkeletalBodySetups.Num());
|
||||
return PhysicsAsset;
|
||||
#else
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine vehicle: physics asset generation is editor-only"));
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "GigLineVehicleBuilder.generated.h"
|
||||
|
||||
class UPhysicsAsset;
|
||||
class USkeletalMesh;
|
||||
|
||||
/**
|
||||
* Script-callable helpers for vehicle setup.
|
||||
*
|
||||
* Chaos Vehicles need a PhysicsAsset, and Unreal's `PhysicsAssetFactory` exposes no mesh
|
||||
* property to Python, so the asset cannot be generated from a commandlet. The body does
|
||||
* the job with `FPhysicsAssetUtils::CreateFromSkeletalMesh`, which is the same code path
|
||||
* the editor's "Create Physics Asset" action uses.
|
||||
*
|
||||
* Editor only: it is compiled into the editor target and never shipped.
|
||||
*/
|
||||
UCLASS()
|
||||
class GIGLINE_API UGigLineVehicleBuilder : public UBlueprintFunctionLibrary
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
/**
|
||||
* Build (or rebuild) a physics asset for `Mesh` at `PackagePath`/`AssetName`.
|
||||
*
|
||||
* @return the created asset, or nullptr with the reason written to the log.
|
||||
*/
|
||||
UFUNCTION(BlueprintCallable, Category = "GigLine|Vehicle")
|
||||
static UPhysicsAsset* BuildPhysicsAsset(USkeletalMesh* Mesh, const FString& PackagePath,
|
||||
const FString& AssetName);
|
||||
};
|
||||
Reference in new issue
Block a user