Two more probes, both negative: --novehmover movement component tick disabled from the constructor -> the pawn still rises overlaps GetOverlappingComponents at spawn -> 0 components So it is not the Chaos movement component, and the body is not inside anything, at any height. With the velocity clamped to zero, the mover disabled, zero overlaps and a correct dynamic body, the transform is still written every frame - in DemoMap only. That is the limit of targeted probing. The remaining path is systematic rather than clever: rebuild DemoMap in a fresh empty level and add one group of staged content at a time, running the pawn profile after each, until it starts rising. Five or six runs name the actor or mesh; guessing at engine internals has not, in ten probes. The probes stay in the tree as switches: -probes (all diagnostics), -novehmover, -profile, plus the velocity clamp and overlap list. They cost nothing when unused.
586 lines
22 KiB
C++
586 lines
22 KiB
C++
#include "GigLineVehicle.h"
|
|
|
|
#include "GigLineVehicleMovement.h"
|
|
#include "WheeledVehiclePawn.h"
|
|
|
|
#include "ChaosVehicleWheel.h"
|
|
#include "ChaosWheeledVehicleMovementComponent.h"
|
|
#include "Components/SkeletalMeshComponent.h"
|
|
#include "PhysicsEngine/BodySetup.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;
|
|
}
|
|
|
|
UGigLineFrontWheel::UGigLineFrontWheel()
|
|
{
|
|
// Dimensions from the model's report: radius 33 cm, width 22 cm. Steering yes, drive no.
|
|
WheelRadius = 33.0f;
|
|
WheelWidth = 22.0f;
|
|
bAffectedBySteering = true;
|
|
bAffectedByEngine = false;
|
|
MaxSteerAngle = 35.0f;
|
|
SuspensionMaxRaise = 8.0f;
|
|
SuspensionMaxDrop = 8.0f;
|
|
}
|
|
|
|
UGigLineRearWheel::UGigLineRearWheel()
|
|
{
|
|
WheelRadius = 33.0f;
|
|
WheelWidth = 22.0f;
|
|
bAffectedBySteering = false;
|
|
bAffectedByEngine = true;
|
|
SuspensionMaxRaise = 8.0f;
|
|
SuspensionMaxDrop = 8.0f;
|
|
}
|
|
|
|
AGigLineVehicle::AGigLineVehicle(const FObjectInitializer& ObjectInitializer)
|
|
// Substitute the movement component class, which is the only place the Chaos rig can be
|
|
// instrumented: AWheeledVehiclePawn creates it, and the base name is how you replace it.
|
|
: Super(ObjectInitializer.SetDefaultSubobjectClass<UGigLineVehicleMovement>(
|
|
AWheeledVehiclePawn::VehicleMovementComponentName))
|
|
{
|
|
PrimaryActorTick.bCanEverTick = true;
|
|
|
|
// Test: if disabling the movement component's tick stops the rise, the component is the writer.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("novehmover")))
|
|
{
|
|
if (UChaosVehicleMovementComponent* Mover = FindComponentByClass<UChaosVehicleMovementComponent>())
|
|
{
|
|
Mover->SetComponentTickEnabled(false);
|
|
Mover->Deactivate();
|
|
}
|
|
}
|
|
|
|
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::BuildVisuals()
|
|
{
|
|
// One component per authored system: body, glass, front and rear lights, mirrors and
|
|
// the four wheels. Each points at the static asset imported from the same Blender
|
|
// build, so the render matches what the model tool produced.
|
|
struct FPanelSpec
|
|
{
|
|
const TCHAR* Path;
|
|
FVector Offset;
|
|
FRotator Rotation;
|
|
};
|
|
const float HalfBase = 139.0f; // wheelbase 278 cm / 2
|
|
const float HalfTrack = 81.0f; // track 162 cm / 2
|
|
const FPanelSpec Specs[] = {
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Body.SM_Vehicle_Body"), FVector::ZeroVector, FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Glass.SM_Vehicle_Glass"), FVector::ZeroVector, FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Light_Front.SM_Vehicle_Light_Front"), FVector::ZeroVector, FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Light_Rear.SM_Vehicle_Light_Rear"), FVector::ZeroVector, FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Mirrors.SM_Vehicle_Mirrors"), FVector::ZeroVector, FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Wheel.SM_Vehicle_Wheel"), FVector(HalfBase, HalfTrack, -16.f), FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Wheel.SM_Vehicle_Wheel"), FVector(HalfBase, -HalfTrack, -16.f), FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Wheel.SM_Vehicle_Wheel"), FVector(-HalfBase, HalfTrack, -16.f), FRotator::ZeroRotator},
|
|
{TEXT("/Game/Hollywood/Vehicle/SM_Vehicle_Wheel.SM_Vehicle_Wheel"), FVector(-HalfBase, -HalfTrack, -16.f), FRotator::ZeroRotator},
|
|
};
|
|
|
|
int32 Index = 0;
|
|
for (const FPanelSpec& Spec : Specs)
|
|
{
|
|
if (UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, Spec.Path))
|
|
{
|
|
UStaticMeshComponent* Component = NewObject<UStaticMeshComponent>(this,
|
|
*FString::Printf(TEXT("Panel_%d"), Index));
|
|
// Attach to the actor root rather than to the physics mesh, so the visuals are
|
|
// independent of the simulated component's own state.
|
|
Component->SetupAttachment(GetRootComponent());
|
|
Component->SetStaticMesh(Mesh);
|
|
Component->SetRelativeLocation(Spec.Offset);
|
|
Component->SetRelativeRotation(Spec.Rotation);
|
|
Component->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
|
Component->SetMobility(EComponentMobility::Movable);
|
|
Component->RegisterComponent();
|
|
// Runtime-created components need their render state forced: without this the
|
|
// panels registered and reported visible but never reached the renderer.
|
|
Component->MarkRenderStateDirty();
|
|
Component->UpdateBounds();
|
|
Panels.Add(Component);
|
|
++Index;
|
|
}
|
|
}
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: %d visual panels attached"), Panels.Num());
|
|
for (int32 PanelIndex = 0; PanelIndex < Panels.Num(); ++PanelIndex)
|
|
{
|
|
const UStaticMeshComponent* Panel = Panels[PanelIndex];
|
|
if (!Panel)
|
|
{
|
|
continue;
|
|
}
|
|
const FVector Where = Panel->GetComponentLocation();
|
|
const FBoxSphereBounds B = Panel->Bounds;
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: panel %d at (%.0f,%.0f,%.0f) visible=%d extent=%.0f mesh=%s"),
|
|
PanelIndex, Where.X, Where.Y, Where.Z,
|
|
Panel->IsVisible() ? 1 : 0, B.BoxExtent.GetMax(),
|
|
Panel->GetStaticMesh() ? *Panel->GetStaticMesh()->GetName() : TEXT("NONE"));
|
|
}
|
|
}
|
|
|
|
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 class.
|
|
Setup.WheelClass = Spec.bSteer ? UGigLineFrontWheel::StaticClass()
|
|
: UGigLineRearWheel::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();
|
|
|
|
// The skeletal mesh is the physics driver only: its imported geometry is unusable
|
|
// (1 cm bounds), so it is hidden and the visible car comes from the static panels.
|
|
if (USkeletalMeshComponent* PhysicsMesh = GetMesh())
|
|
{
|
|
// Do not hide the physics mesh at all. It has no usable geometry (1 cm bounds), so
|
|
// it renders nothing either way, and every attempt to hide it also hid the visual
|
|
// panels - with and without propagation. Leaving it visible avoids the fight.
|
|
PhysicsMesh->SetCastShadow(false);
|
|
}
|
|
BuildVisuals();
|
|
|
|
// Where is the road, really? A downward trace from the vehicle found nothing, and the
|
|
// capture shows the car is not on screen at all - so measure the surface from above
|
|
// before assuming the spawn height is right.
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
const FVector From = GetActorLocation() + FVector(0.f, 0.f, 1000.f);
|
|
FHitResult Hit;
|
|
FCollisionQueryParams Q(TEXT("GigLineVehicleRoad"), false, this);
|
|
const bool bHit = World->LineTraceSingleByChannel(Hit, From,
|
|
From - FVector(0.f, 0.f, 4000.f), ECC_Visibility, Q);
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: surface below spawn at Z %.0f cm (hit=%d, actor at %.0f)"),
|
|
bHit ? Hit.ImpactPoint.Z : -9999.f, bHit ? 1 : 0, GetActorLocation().Z);
|
|
}
|
|
|
|
// Wheel objects do not exist until the component is registered, so the per-wheel
|
|
// flags set in the constructor went nowhere - which is why the drive test measured
|
|
// zero speed with throttle held. Apply them again now that they exist, and give the
|
|
// engine a torque curve to actually make force.
|
|
if (UChaosWheeledVehicleMovementComponent* Movement =
|
|
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
|
{
|
|
ConfigureWheels();
|
|
Movement->EngineSetup.MaxTorque = 500.0f;
|
|
Movement->EngineSetup.MaxRPM = 6000.0f;
|
|
Movement->EngineSetup.EngineIdleRPM = 900.0f;
|
|
Movement->TransmissionSetup.bUseAutomaticGears = true;
|
|
Movement->TransmissionSetup.FinalRatio = 3.5f;
|
|
Movement->SetThrottleInput(0.0f);
|
|
if (const USkeletalMeshComponent* BodyComp = GetMesh())
|
|
{
|
|
const USkeletalMesh* Asset = BodyComp->GetSkeletalMeshAsset();
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: mesh=%s visible=%d hidden=%d bounds=%s"),
|
|
Asset ? *Asset->GetName() : TEXT("NONE"),
|
|
BodyComp->IsVisible() ? 1 : 0,
|
|
BodyComp->bHiddenInGame ? 1 : 0,
|
|
*BodyComp->Bounds.BoxExtent.ToString());
|
|
}
|
|
// Name the bones and check each setup against them: Chaos silently creates no wheel
|
|
// when a setup's bone is not on the mesh, which is how four setups produced zero
|
|
// wheels without a warning.
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
TArray<FName> Bones;
|
|
Body->GetBoneNames(Bones);
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: %d bones, e.g. %s"),
|
|
Bones.Num(),
|
|
Bones.Num() > 0 ? *Bones[FMath::Min(1, Bones.Num() - 1)].ToString() : TEXT("none"));
|
|
for (const FChaosWheelSetup& Setup : Movement->WheelSetups)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: setup bone '%s' index %d"),
|
|
*Setup.BoneName.ToString(), Body->GetBoneIndex(Setup.BoneName));
|
|
}
|
|
}
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: %d wheel objects at BeginPlay (wheels are created later, "
|
|
"with the physics state), torque %.0f"),
|
|
Movement->Wheels.Num(), Movement->EngineSetup.MaxTorque);
|
|
}
|
|
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)
|
|
{
|
|
// Which writer moves the actor? Sample the transform before and after the base class tick:
|
|
// a change between the two happened inside APawn::Tick (movement component, attachment
|
|
// update, animation), a change between this frame's exit and the next frame's entry came
|
|
// from somewhere else entirely.
|
|
const FVector BeforeSuper = GetActorLocation();
|
|
|
|
Super::Tick(DeltaSeconds);
|
|
|
|
const FVector AfterSuper = GetActorLocation();
|
|
if (!AfterSuper.Equals(BeforeSuper, 1.0))
|
|
{
|
|
static float LoggedThisPhase = -1.0f;
|
|
if (SettleElapsed - LoggedThisPhase > 0.25f)
|
|
{
|
|
LoggedThisPhase = SettleElapsed;
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle writer: inside Super::Tick %.0f -> %.0f (dz %.0f)"),
|
|
BeforeSuper.Z, AfterSuper.Z, AfterSuper.Z - BeforeSuper.Z);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// `-drivetest`: hold full throttle from 1 s to 4 s and report the result. A vehicle
|
|
// that spawns is not a vehicle that drives.
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("drivetest")))
|
|
{
|
|
DriveElapsed += DeltaSeconds;
|
|
if (UChaosVehicleMovementComponent* Movement = GetVehicleMovementComponent())
|
|
{
|
|
if (DriveElapsed > 1.0f && DriveElapsed < 4.0f)
|
|
{
|
|
Movement->SetThrottleInput(1.0f);
|
|
}
|
|
else if (DriveElapsed >= 4.0f && !bDriveReported)
|
|
{
|
|
Movement->SetThrottleInput(0.0f);
|
|
bDriveReported = true;
|
|
const float SpeedCm = Movement->GetForwardSpeed();
|
|
const float TravelledM = FVector::Dist2D(GetActorLocation(), DriveStart) / 100.f;
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle DRIVETEST speed=%.0f cm/s (%.1f km/h), travelled %.1f m -> %s"),
|
|
SpeedCm, SpeedCm * 0.036f, TravelledM,
|
|
(SpeedCm > 200.f && TravelledM > 1.0f) ? TEXT("PASS") : TEXT("FAIL"));
|
|
}
|
|
}
|
|
}
|
|
if (DriveStart.IsZero() && DriveElapsed > 1.0f)
|
|
{
|
|
DriveStart = GetActorLocation();
|
|
}
|
|
|
|
// A real-car mass. Without it the physics asset's box takes its computed mass, and a light
|
|
// body under the vehicle solver is thrown around instead of settling - which is consistent
|
|
// with a pawn that launches itself and never creates its wheels.
|
|
if (!bMassSet && SettleElapsed > 0.5f)
|
|
{
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
Body->SetMassOverrideInKg(NAME_None, 1500.0f, true);
|
|
bMassSet = true;
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: mass override 1500 kg, mass now %.0f"),
|
|
Body->GetMass());
|
|
}
|
|
}
|
|
|
|
// Is the body actually dynamic? A kinematic body is teleported by whoever holds it and reports
|
|
// no velocity - exactly the profile measured - so this decides the next step.
|
|
{
|
|
static bool bDynamicsChecked = false;
|
|
if (!bDynamicsChecked && SettleElapsed > 0.2f)
|
|
{
|
|
bDynamicsChecked = true;
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
FBodyInstance* Instance = Body->GetBodyInstance();
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle dynamic: simulate=%d instance=%s profile=%s"),
|
|
Body->IsSimulatingPhysics() ? 1 : 0,
|
|
Instance ? TEXT("yes") : TEXT("no"),
|
|
Body->GetCollisionProfileName().IsNone()
|
|
? TEXT("none") : *Body->GetCollisionProfileName().ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kill any injected velocity every frame for the first two seconds. If the pawn still rises, the
|
|
// writer sets the transform directly; if it settles, the whole thing was one bad initial impulse.
|
|
if (SettleElapsed < 2.0f)
|
|
{
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
Body->SetPhysicsLinearVelocity(FVector::ZeroVector);
|
|
Body->SetPhysicsAngularVelocityInDegrees(FVector::ZeroVector);
|
|
}
|
|
}
|
|
|
|
// Name what the body is actually inside. A single huge collision proxy somewhere in this map
|
|
// would explain a positional push at any height.
|
|
{
|
|
static bool bOverlapLogged = false;
|
|
if (!bOverlapLogged && SettleElapsed > 0.3f)
|
|
{
|
|
bOverlapLogged = true;
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
TArray<UPrimitiveComponent*> Overlaps;
|
|
Body->GetOverlappingComponents(Overlaps);
|
|
FString List;
|
|
for (const UPrimitiveComponent* Comp : Overlaps)
|
|
{
|
|
List += FString::Printf(TEXT("%s/%s "),
|
|
Comp->GetOwner() ? *Comp->GetOwner()->GetName() : TEXT("?"),
|
|
*Comp->GetName());
|
|
}
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle overlaps: %d [%s]"),
|
|
Overlaps.Num(), *List);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Velocity profile: an impulse shows as a spike at t=0, a force as steady growth.
|
|
{
|
|
static float NextSample = 0.0f;
|
|
if (SettleElapsed > NextSample && SettleElapsed < 2.5f)
|
|
{
|
|
NextSample += 0.25f;
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
const FVector V = Body->GetPhysicsLinearVelocity();
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle profile t=%.2f Z=%.0f vZ=%.0f vY=%.0f"),
|
|
SettleElapsed, GetActorLocation().Z, V.Z, V.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Decisive test: if the pawn keeps rising with physics switched OFF, physics is not the mover.
|
|
if (!bPhysicsOff && SettleElapsed > 3.0f)
|
|
{
|
|
bPhysicsOff = true;
|
|
if (USkeletalMeshComponent* Body = GetMesh())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: physics OFF at Z %.0f (component velocity %.0f)"),
|
|
GetActorLocation().Z, Body->GetComponentVelocity().Z);
|
|
Body->SetSimulatePhysics(false);
|
|
}
|
|
}
|
|
if (bPhysicsOff && !bPhysicsOffLogged && SettleElapsed > 4.0f)
|
|
{
|
|
bPhysicsOffLogged = true;
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle: after 1 s without physics, Z %.0f"),
|
|
GetActorLocation().Z);
|
|
}
|
|
|
|
// If the Chaos vehicle never came into existence (no wheels), its movement component has no
|
|
// valid state to drive, and continuing to tick it writes whatever is in that empty state back
|
|
// onto the actor - which is the only explanation that fits a still body and a moving actor.
|
|
if (!bMoverStopped && SettleElapsed > 2.5f)
|
|
{
|
|
bMoverStopped = true;
|
|
if (const UChaosWheeledVehicleMovementComponent* WheelComp =
|
|
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
|
{
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: stopping the mover - wheels=%d at Z %.0f"),
|
|
WheelComp->Wheels.Num(), GetActorLocation().Z);
|
|
}
|
|
if (UMovementComponent* Mover = GetVehicleMovementComponent())
|
|
{
|
|
Mover->SetComponentTickEnabled(false);
|
|
}
|
|
}
|
|
|
|
// One-shot diagnostic: why zero wheels. Wheels are built in CreateVehicle(), which runs only
|
|
// when the physics state produced a vehicle, so the useful facts are whether the mesh is
|
|
// simulating and whether it carries a physics asset at all.
|
|
{
|
|
static bool bDiagnosed = false;
|
|
if (!bDiagnosed && SettleElapsed > 1.5f && FParse::Param(FCommandLine::Get(), TEXT("probes")))
|
|
{
|
|
bDiagnosed = true;
|
|
USkeletalMeshComponent* Body = GetMesh();
|
|
const UChaosWheeledVehicleMovementComponent* Wheeled =
|
|
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent());
|
|
FString BoxInfo = TEXT("no-box");
|
|
if (Body && Body->GetBodySetup() && Body->GetBodySetup()->AggGeom.BoxElems.Num() > 0)
|
|
{
|
|
const FKBoxElem& Box = Body->GetBodySetup()->AggGeom.BoxElems[0];
|
|
BoxInfo = FString::Printf(TEXT("box %.0fx%.0fx%.0f centre %.0f,%.0f,%.0f"),
|
|
Box.X, Box.Y, Box.Z, Box.Center.X, Box.Center.Y, Box.Center.Z);
|
|
}
|
|
FString BoundsInfo = TEXT("no-bounds");
|
|
if (Body)
|
|
{
|
|
const FBoxSphereBounds B = Body->Bounds;
|
|
BoundsInfo = FString::Printf(TEXT("bounds origin %.0f,%.0f,%.0f extent %.0f,%.0f,%.0f"),
|
|
B.Origin.X, B.Origin.Y, B.Origin.Z, B.BoxExtent.X, B.BoxExtent.Y, B.BoxExtent.Z);
|
|
}
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle geometry: %s | %s"), *BoxInfo, *BoundsInfo);
|
|
if (const UChaosVehicleMovementComponent* Mover = GetVehicleMovementComponent())
|
|
{
|
|
const auto& Output = const_cast<UChaosVehicleMovementComponent*>(Mover)->PhysicsVehicleOutput();
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle output: %s"),
|
|
Output.IsValid() ? TEXT("present") : TEXT("null"));
|
|
}
|
|
if (Body)
|
|
{
|
|
const FVector Vel = Body->GetPhysicsLinearVelocity();
|
|
const FVector Angular = Body->GetPhysicsAngularVelocityInDegrees();
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle motion: velocity %.0f,%.0f,%.0f angular %.0f,%.0f,%.0f "
|
|
"mass %.0f sim=%d kinematic=%d"),
|
|
Vel.X, Vel.Y, Vel.Z, Angular.X, Angular.Y, Angular.Z, Body->GetMass(),
|
|
Body->IsSimulatingPhysics() ? 1 : 0, Body->IsAnySimulatingPhysics() ? 1 : 0);
|
|
}
|
|
if (Body && Body->GetSkeletalMeshAsset())
|
|
{
|
|
// Do the wheel setups name bones the mesh actually has? If not, CreateVehicle()
|
|
// bails before it ever builds a wheel.
|
|
TArray<FName> Bones;
|
|
Body->GetBoneNames(Bones);
|
|
FString BoneList;
|
|
for (int32 i = 0; i < Bones.Num() && i < 20; ++i)
|
|
{
|
|
BoneList += Bones[i].ToString() + TEXT(" ");
|
|
}
|
|
FString SetupList;
|
|
if (const UChaosWheeledVehicleMovementComponent* WheelComp =
|
|
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
|
{
|
|
for (const FChaosWheelSetup& S : WheelComp->WheelSetups)
|
|
{
|
|
SetupList += S.BoneName.ToString() + TEXT(" ");
|
|
}
|
|
}
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle bones: %d [%s] | setups [%s]"),
|
|
Bones.Num(), *BoneList, *SetupList);
|
|
if (const UMovementComponent* Mover = GetVehicleMovementComponent())
|
|
{
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle mover: updated=%s ticking=%d"),
|
|
Mover->UpdatedComponent ? *Mover->UpdatedComponent->GetName() : TEXT("none"),
|
|
Mover->IsComponentTickEnabled() ? 1 : 0);
|
|
}
|
|
}
|
|
{
|
|
const USkeletalMesh* Asset = Body ? Body->GetSkeletalMeshAsset() : nullptr;
|
|
UE_LOG(LogTemp, Warning, TEXT("GigLine vehicle mesh asset: %s"),
|
|
Asset ? *Asset->GetName() : TEXT("NONE"));
|
|
}
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle diag: mesh=%d simulating=%d physasset=%s setups=%d wheels=%d "
|
|
"loc=%.0f,%.0f,%.0f scale=%.2f mass=%.0f"),
|
|
Body != nullptr,
|
|
Body && Body->IsSimulatingPhysics() ? 1 : 0,
|
|
Body && Body->GetBodySetup() ? TEXT("yes") : TEXT("no"),
|
|
Wheeled ? Wheeled->WheelSetups.Num() : -1,
|
|
Wheeled ? Wheeled->Wheels.Num() : -1,
|
|
GetActorLocation().X, GetActorLocation().Y, GetActorLocation().Z,
|
|
GetActorScale3D().Z, Body ? Body->GetMass() : -1.0f);
|
|
}
|
|
}
|
|
|
|
SettleElapsed += DeltaSeconds;
|
|
if (!bSettleReported && SettleElapsed > 3.0f)
|
|
{
|
|
bSettleReported = true;
|
|
if (const UChaosWheeledVehicleMovementComponent* Wheeled =
|
|
Cast<UChaosWheeledVehicleMovementComponent>(GetVehicleMovementComponent()))
|
|
{
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: %d wheel objects once physics exists"),
|
|
Wheeled->Wheels.Num());
|
|
}
|
|
if (UWorld* World = GetWorld())
|
|
{
|
|
FHitResult Hit;
|
|
FCollisionQueryParams Q(TEXT("GigLineVehicleGround"), false, this);
|
|
const bool bGround = World->LineTraceSingleByChannel(Hit, GetActorLocation(),
|
|
GetActorLocation() - FVector(0.f, 0.f, 2000.f), ECC_Visibility, Q);
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: ground at Z %.0f cm, actor %.0f cm, height above ground %.0f cm"),
|
|
bGround ? Hit.ImpactPoint.Z : -9999.f, GetActorLocation().Z,
|
|
bGround ? GetActorLocation().Z - Hit.ImpactPoint.Z : -9999.f);
|
|
}
|
|
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;
|
|
}
|