Compare commits
13
Commits
v0.2.3
..
cb94438e0d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb94438e0d | ||
|
|
bb8ae3eb4b | ||
|
|
a17e982561 | ||
|
|
b50afa4bcd | ||
|
|
021aa56121 | ||
|
|
1cddfbfc68 | ||
|
|
627737062b | ||
|
|
5dd53ac30b | ||
|
|
4f3ed20e49 | ||
|
|
2e0421480c | ||
|
|
02cadd4964 | ||
|
|
a72bf85b2e | ||
|
|
73b5bd6cd1 |
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -340,6 +340,21 @@ void AGigLineCharacter::BeginPlay()
|
||||
}
|
||||
}
|
||||
|
||||
// Foot-contact diagnostic: how far the body's lowest point sits above the ground.
|
||||
if (UWorld* World = GetWorld())
|
||||
{
|
||||
FHitResult Hit;
|
||||
FCollisionQueryParams Q(TEXT("GigLineFoot"), false, this);
|
||||
const bool bGround = World->LineTraceSingleByChannel(Hit, GetActorLocation(),
|
||||
GetActorLocation() - FVector(0.f, 0.f, 3000.f), ECC_Visibility, Q);
|
||||
const float CapsuleHalf = GetCapsuleComponent() ? GetCapsuleComponent()->GetScaledCapsuleHalfHeight() : -1.f;
|
||||
const float MeshBottom = GetMesh() ? (GetMesh()->Bounds.Origin - GetMesh()->Bounds.BoxExtent).Z : 0.f;
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine foot: actor Z %.0f, ground %.0f, capsule half %.0f, mesh bottom %.0f, gap %.0f cm"),
|
||||
GetActorLocation().Z, bGround ? Hit.ImpactPoint.Z : -9999.f, CapsuleHalf, MeshBottom,
|
||||
bGround ? MeshBottom - Hit.ImpactPoint.Z : -9999.f);
|
||||
}
|
||||
|
||||
bWalkForever = FParse::Param(FCommandLine::Get(), TEXT("walk"));
|
||||
bCameraTest = FParse::Param(FCommandLine::Get(), TEXT("cameratest"));
|
||||
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#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;
|
||||
|
||||
/**
|
||||
* Visual panels, as static mesh components.
|
||||
*
|
||||
* The skeletal vehicle asset imported with no usable geometry (runtime bounds of
|
||||
* 1 cm), so the car was invisible in the scene while its physics body still cast a
|
||||
* shadow - the "no vehicle in scene" report. The static assets are correct (body
|
||||
* 476 x 176 x 128 cm), so the visible car is assembled from them and the skeletal
|
||||
* mesh stays as the physics/animation driver only.
|
||||
*/
|
||||
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Visual")
|
||||
TArray<TObjectPtr<UStaticMeshComponent>> Panels;
|
||||
|
||||
/** 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();
|
||||
|
||||
/** Attach the visible panels from the imported static systems. */
|
||||
void BuildVisuals();
|
||||
|
||||
float DriveElapsed = 0.f;
|
||||
bool bDriveReported = false;
|
||||
FVector DriveStart = FVector::ZeroVector;
|
||||
|
||||
float SettleElapsed = 0.f;
|
||||
bool bSettleReported = false;
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
#include "GigLineVehicleBuilder.h"
|
||||
|
||||
#if WITH_EDITOR
|
||||
#include "AssetToolsModule.h"
|
||||
#include "Engine/SkeletalMesh.h"
|
||||
#include "PhysicsAssetUtils.h"
|
||||
#include "PhysicsEngine/PhysicsAsset.h"
|
||||
#include "PhysicsEngine/BodySetup.h"
|
||||
#include "PhysicsEngine/SkeletalBodySetup.h"
|
||||
#include "Engine/SkeletalMesh.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);
|
||||
|
||||
// ONE body on the chassis bone, which is what Unreal's own vehicle template uses.
|
||||
// The first attempt let FPhysicsAssetUtils build a body per bone and the car settled
|
||||
// 3.7 m above the road: the four wheel bodies were intersecting the chassis body and
|
||||
// pushing the whole vehicle up. Chaos drives the wheels itself, so they must not be
|
||||
// separate rigid bodies here.
|
||||
const FBoxSphereBounds Bounds = Mesh->GetBounds();
|
||||
const FVector Extent = Bounds.BoxExtent;
|
||||
const FName ChassisBone(TEXT("root"));
|
||||
|
||||
USkeletalBodySetup* Body = NewObject<USkeletalBodySetup>(PhysicsAsset, ChassisBone,
|
||||
RF_Public | RF_Transactional);
|
||||
Body->BoneName = ChassisBone;
|
||||
// A box slightly under the mesh bounds: a body that is exactly the bounds sits on the
|
||||
// ground plane and reads as floating because the mesh has wheels below the sills.
|
||||
FKBoxElem Box;
|
||||
Box.X = Extent.X * 1.9f;
|
||||
Box.Y = Extent.Y * 1.8f;
|
||||
Box.Z = Extent.Z * 0.55f;
|
||||
Box.Center = FVector(0.f, 0.f, -Extent.Z * 0.25f);
|
||||
Body->AggGeom.BoxElems.Add(Box);
|
||||
PhysicsAsset->SkeletalBodySetups.Add(Body);
|
||||
|
||||
UE_LOG(LogTemp, Warning,
|
||||
TEXT("GigLine vehicle: chassis body %.0f x %.0f x %.0f cm on bone '%s'"),
|
||||
Box.X, Box.Y, Box.Z, *ChassisBone.ToString());
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
# Blender vs in-game
|
||||
|
||||
Generated by `Tools/build_comparison_md.py` — do not edit by hand.
|
||||
|
||||
- In-game captures: `Screenshots/demo/` (9 files)
|
||||
- Blender renders: `Screenshots/blender/` (22 files), made by `Tools/blender_screenshots.py`
|
||||
|
||||
Left is what the engine draws, right is what Blender has. A gap between them is the import problem, not a taste problem: scale, material colour, missing parts and split systems all show up here before they show up in a review.
|
||||
|
||||
## Side by side
|
||||
|
||||
| In-game | Blender | Shot |
|
||||
| --- | --- | --- |
|
||||
| <img src="demo/review-demo-crossing.png" width="520"> | — | `review-demo-crossing.png` |
|
||||
| <img src="demo/review-demo-north-buildings.png" width="520"> | — | `review-demo-north-buildings.png` |
|
||||
| <img src="demo/review-demo-retail-frontage.png" width="520"> | — | `review-demo-retail-frontage.png` |
|
||||
| <img src="demo/review-demo-spawn-view.png" width="520"> | — | `review-demo-spawn-view.png` |
|
||||
| <img src="demo/review-demo-street-level.png" width="520"> | — | `review-demo-street-level.png` |
|
||||
| <img src="demo/review-demo-third-person-front.png" width="520"> | <img src="blender/character-angle-035.png" width="520"> | `review-demo-third-person-front.png` |
|
||||
| <img src="demo/review-demo-third-person-walk.png" width="520"> | <img src="blender/character-angle-035.png" width="520"> | `review-demo-third-person-walk.png` |
|
||||
| <img src="demo/review-demo-third-person.png" width="520"> | <img src="blender/character-angle-035.png" width="520"> | `review-demo-third-person.png` |
|
||||
| <img src="demo/review-demo-vehicle.png" width="520"> | <img src="blender/vehicle-angle-035.png" width="520"> | `review-demo-vehicle.png` |
|
||||
|
||||
4 of 9 in-game captures have a Blender counterpart; the rest are street and building shots whose meshes come from the Geo pipeline rather than a .blend.
|
||||
|
||||
## Signoff
|
||||
|
||||
One row per in-game screen. `—` in Blender means there is nothing to compare against.
|
||||
|
||||
| # | In-game screen | Blender counterpart | Verdict | Notes |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | `review-demo-crossing.png` | — | ☐ | |
|
||||
| 2 | `review-demo-north-buildings.png` | — | ☐ | |
|
||||
| 3 | `review-demo-retail-frontage.png` | — | ☐ | |
|
||||
| 4 | `review-demo-spawn-view.png` | — | ☐ | |
|
||||
| 5 | `review-demo-street-level.png` | — | ☐ | |
|
||||
| 6 | `review-demo-third-person-front.png` | `character-angle-035.png` | ☐ | |
|
||||
| 7 | `review-demo-third-person-walk.png` | `character-angle-035.png` | ☐ | |
|
||||
| 8 | `review-demo-third-person.png` | `character-angle-035.png` | ☐ | |
|
||||
| 9 | `review-demo-vehicle.png` | `vehicle-angle-035.png` | ☐ | |
|
||||
|
||||
Verdicts: ☐ pending · ☑ approved · ☒ rejected. The authoritative per-shot review stays in [README.md](README.md); this page exists to compare Blender against the engine.
|
||||
@@ -26,6 +26,7 @@ Every shot is embedded below, in order, so the whole review happens in this file
|
||||
| 6 | [Third-person body](#6-third-person-body) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||
| 7 | [Third-person front](#7-third-person-front) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||
| 8 | [Third-person walk](#8-third-person-walk) | ☑ 2026-11-09 | ☑ 2026-11-09 |
|
||||
| 9 | [Vehicle](#9-vehicle) | ☐ FAIL 2026-11-09 | ☐ pending |
|
||||
|
||||
**Verified** is what `--verify` reports: the frame is not blank, is the right size, and has tonal range. **Approved** is the owner's verdict, recorded here. Each shot also has an **expectation** in [`Tools/visual_targets.json`](../Tools/visual_targets.json) describing the content it must show.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loaded 100 of 118 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user