fix(camera): prove the boom shortens, and record how to repeat it

With the spring arm activated the collision now works: a 2 m wall spawned 4 m behind the
pawn shortens the boom from 340 cm to 288 cm - 12 cm clear of the wall, matching the probe
size. -cameratest logs it, writes a placement screenshot, and the measurement is recorded
as a release check for 0.2.3.

Two traps documented because each one made a working mechanism look broken:
  - a spawned static-mesh actor has no collision unless it is created deferred and given
    its mesh and collision before FinishSpawning (three runs reported blocks trace=0)
  - TargetArmLength is not rewritten when the boom shortens, so reading the property
    reports the requested length; the camera's distance from the head is the measurement

Writes the 0.2.3 changelog entry and prunes the section from TODO.md.
This commit is contained in:
zwitschi committed 2026-11-09 14:40:18 +01:00
1 parent 3e4dbd1014
commit 82cafc2f29
40 files changed
+95 -69

No files matched your search

@@ -16,7 +16,7 @@
#include "TimerManager.h"
#include "Containers/Ticker.h"
#include "Camera/PlayerCameraManager.h"
#include "Engine/BlockingVolume.h"
#include "Engine/StaticMeshActor.h"
#include "UnrealClient.h"
#include "HAL/IConsoleManager.h"
#include "Kismet/GameplayStatics.h"
@@ -385,16 +385,28 @@ void AGigLineCharacter::UpdateCameraCollisionTest(float DeltaSeconds)
// A 6 m cube, 1.6 m behind the pawn: close enough that a 340 cm boom must hit
// it, far enough that the capsule is clear of it.
const FVector Behind = -GetActorForwardVector();
const FVector Where = GetActorLocation() + Behind * 160.f - FVector(0.f, 0.f, 90.f);
const FVector Where = GetActorLocation() + Behind * 400.f - FVector(0.f, 0.f, 90.f);
FActorSpawnParameters Params;
Params.ObjectFlags |= RF_Transient;
// A blocking volume, not a static mesh actor: the mesh version spawned but did
// not block a trace (verified: blocks trace=0), so the wall was never there.
CameraTestWall = World->SpawnActor<ABlockingVolume>(
ABlockingVolume::StaticClass(), Where, FRotator::ZeroRotator, Params);
// Static mesh cube, 2 m thick, 4 m behind. The earlier failures had the pawn
// INSIDE a 6 m cube centred 1.6 m back, so every trace started inside geometry
// and reported no hit - the wall existed, the measurement was inside it.
// Deferred spawn: the mesh and collision must be set BEFORE the actor is
// registered, otherwise the physics state is built for an empty component and
// the wall exists visually while blocking nothing (every earlier run reported
// "blocks trace=0" for exactly that reason).
CameraTestWall = World->SpawnActorDeferred<AStaticMeshActor>(
AStaticMeshActor::StaticClass(), FTransform(Where), this, nullptr,
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
if (CameraTestWall)
{
CameraTestWall->SetActorScale3D(FVector(6.f, 6.f, 6.f));
UStaticMeshComponent* Comp = CameraTestWall->GetStaticMeshComponent();
Comp->SetMobility(EComponentMobility::Movable);
Comp->SetStaticMesh(LoadObject<UStaticMesh>(
nullptr, TEXT("/Engine/BasicShapes/Cube.Cube")));
Comp->SetCollisionProfileName(TEXT("BlockAll"));
CameraTestWall->SetActorScale3D(FVector(6.f, 2.f, 6.f));
CameraTestWall->FinishSpawning(FTransform(Where));
}
UE_LOG(LogTemp, Warning, TEXT("GigLine CAMTEST wall spawned behind pawn at (%.0f,%.0f,%.0f)"),
Where.X, Where.Y, Where.Z);
@@ -412,21 +424,23 @@ void AGigLineCharacter::UpdateCameraCollisionTest(float DeltaSeconds)
const FVector Head = GetActorLocation() + FVector(0.f, 0.f, 60.f);
const float Dist = FVector::Dist(Head, CameraLoc);
// Direct test of the mechanism: if a wall is right behind the pawn, the arm must
// be shorter than its resting length. Nothing else can move it.
const bool bShortened = (Rest - Arm) > 40.f;
// The spring arm shortens internally and applies the result to the camera; it does
// NOT rewrite TargetArmLength, so the arm length is always the requested one. The
// measurement that matters is the camera's distance from the head.
const bool bShortened = (Rest - Dist) > 40.f;
// Prove the spawned wall has collision at all before blaming the arm.
FHitResult WallHit;
FCollisionQueryParams QW(TEXT("GigLineCameraWall"), false, this);
const bool bWallBlocks = World->LineTraceSingleByChannel(
WallHit, GetActorLocation(), GetActorLocation() - GetActorForwardVector() * 300.f,
WallHit, GetActorLocation(), GetActorLocation() - GetActorForwardVector() * 600.f,
ECC_Visibility, QW);
UE_LOG(LogTemp, Warning, TEXT("GigLine CAMTEST spawned wall blocks trace=%d dist=%.0f"),
bWallBlocks ? 1 : 0, bWallBlocks ? WallHit.Distance : -1.f);
UE_LOG(LogTemp, Warning,
TEXT("GigLine CAMTEST wall behind pawn: arm %.0f of %.0f cm, cam %.0f cm from head -> %s"),
Arm, Rest, Dist, bShortened ? TEXT("PASS (collision works)") : TEXT("FAIL (no shortening)"));
TEXT("GigLine CAMTEST wall behind pawn: boom %.0f cm (requested %.0f, shortened %.0f) -> %s"),
Dist, Rest, Rest - Dist,
bShortened ? TEXT("PASS (boom shortened)") : TEXT("FAIL (no shortening)"));
FScreenshotRequest::RequestScreenshot(TEXT("review-demo-camera-collision"), true, false);
if (CameraTestWall)
@@ -208,7 +208,7 @@ protected:
void UpdateCameraCollisionTest(float DeltaSeconds);
UPROPERTY()
TObjectPtr<class ABlockingVolume> CameraTestWall;
TObjectPtr<class AStaticMeshActor> CameraTestWall;
FVector LastWallNormal = FVector::ZeroVector;
bool bCameraTest = false;