Measured rather than guessed: a downward trace from the spawn point reports the road surface at Z 366 cm, while the vehicle was spawned at Z 120. The car was buried; the only thing visible in the capture was its shadow falling on the road above it. Spawn is now Z 470, the camera aims at 430, and it settles at Z 470. Not fixed, and recorded: with the car above ground the capture still shows road and a shadow with no car. Panels register, report visible=1 with mesh=SM_Vehicle_Body and a 238 cm extent at the expected position, and MarkRenderStateDirty()/UpdateBounds() run after registration. The shadow proves a collider is there; the visible geometry is not. Screenshots/README.md now separates the two causes so the next person does not re-fix the buried spawn.
289 lines
11 KiB
C++
289 lines
11 KiB
C++
#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::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 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();
|
|
|
|
// 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());
|
|
}
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("GigLine vehicle: %d wheel objects after register, 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)
|
|
{
|
|
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.
|
|
// `-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();
|
|
}
|
|
|
|
SettleElapsed += DeltaSeconds;
|
|
if (!bSettleReported && SettleElapsed > 3.0f)
|
|
{
|
|
bSettleReported = true;
|
|
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;
|
|
}
|