CARLA
Loading...
Searching...
No Matches
CarlaWheeledVehicle.cpp
Go to the documentation of this file.
1// Copyright (c) 2026 Computer Vision Center (CVC) at the Universitat Autonoma
2// de Barcelona (UAB).
3// Copyright (c) 2019 Intel Corporation
4//
5// This work is licensed under the terms of the MIT license.
6// For a copy, see <https://opensource.org/licenses/MIT>.
7
9
10#include "Carla.h"
12#include "Carla/Game/CarlaHUD.h"
22
23#include <util/ue-header-guard-begin.h>
24#include "Components/BoxComponent.h"
25#include "Components/LightComponent.h"
26#include "Components/StaticMeshComponent.h"
27#include "Engine/CollisionProfile.h"
28#include "UObject/UnrealType.h"
30#include "Rendering/SkeletalMeshRenderData.h"
31#include "VehicleAnimationInstance.h"
32#include "UObject/UObjectGlobals.h"
33#include "DrawDebugHelpers.h"
34#include "Kismet/KismetSystemLibrary.h"
35#include "PhysicsEngine/PhysicsObjectExternalInterface.h"
36#include "Physics/Experimental/PhysScene_Chaos.h"
37#include <util/ue-header-guard-end.h>
38
39// =============================================================================
40// -- Constructor and destructor -----------------------------------------------
41// =============================================================================
42
43// SnapshotPose fills LocalTransforms with parent-relative bone transforms, so
44// scaling those by the component transform alone never reaches world space.
45static void MakePoseWorldSpace(USkeletalMeshComponent *Mesh, FPoseSnapshot &Pose)
46{
47 if (Mesh == nullptr)
48 {
49 return;
50 }
51 const FTransform ComponentToWorld = Mesh->GetComponentTransform();
52 const int32 NumBones = FMath::Min(Pose.LocalTransforms.Num(), Mesh->GetNumBones());
53 for (int32 BoneIndex = 0; BoneIndex < NumBones; ++BoneIndex)
54 {
55 Pose.LocalTransforms[BoneIndex] = Mesh->GetBoneTransform(BoneIndex, ComponentToWorld);
56 }
57}
58
60{
61 FString ROSMessage = Message;
62 UE_LOG(LogCarla, Warning, TEXT("ROS2 Message received: %s"), *ROSMessage);
63}
64
65ACarlaWheeledVehicle::ACarlaWheeledVehicle(const FObjectInitializer& ObjectInitializer) :
66 Super(ObjectInitializer)
67{
68 VehicleBounds = CreateDefaultSubobject<UBoxComponent>(TEXT("VehicleBounds"));
69 VehicleBounds->SetupAttachment(RootComponent);
70 VehicleBounds->SetHiddenInGame(true);
71 VehicleBounds->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
72
73
74 VelocityControl = CreateDefaultSubobject<UVehicleVelocityControl>(TEXT("VelocityControl"));
75 VelocityControl->Deactivate();
76
77 AccelerationControl = CreateDefaultSubobject<UVehicleAccelerationControl>(TEXT("AccelerationControl"));
78 AccelerationControl->Deactivate();
79
80 GetChaosWheeledVehicleMovementComponent()->bReverseAsBrake = false;
81 BaseMovementComponent = CreateDefaultSubobject<UBaseCarlaMovementComponent>(TEXT("BaseMovementComponent"));
82
83}
84
86
88{
89 Super::BeginPlay();
90
92
94
95 // Get constraint components and their initial transforms
96 FTransform ActorInverseTransform = GetActorTransform().Inverse();
99 ConstraintDoor.Empty();
100 for (FName& ComponentName : ConstraintComponentNames)
101 {
102 UPhysicsConstraintComponent* ConstraintComponent =
103 Cast<UPhysicsConstraintComponent>(GetDefaultSubobjectByName(ComponentName));
104 if (ConstraintComponent)
105 {
106 UPrimitiveComponent* DoorComponent = Cast<UPrimitiveComponent>(
107 GetDefaultSubobjectByName(ConstraintComponent->ComponentName1.ComponentName));
108 if (DoorComponent)
109 {
110 UE_LOG(LogCarla, Log, TEXT("Door name: %s"), *(DoorComponent->GetName()));
111 FTransform ComponentWorldTransform = DoorComponent->GetComponentTransform();
112 FTransform RelativeTransform = ComponentWorldTransform * ActorInverseTransform;
113 DoorComponentsTransform.Add(DoorComponent, RelativeTransform);
114 ConstraintDoor.Add(ConstraintComponent, DoorComponent);
115 ConstraintsComponents.Add(ConstraintComponent);
116 ConstraintComponent->TermComponentConstraint();
117 }
118 else
119 {
120 UE_LOG(LogCarla, Error, TEXT("Missing component for constraint: %s"), *(ConstraintComponent->GetName()));
121 }
122 }
123 }
124 // get collision disable constraints (used to prevent doors from colliding with each other)
126 TArray<UPhysicsConstraintComponent*> Constraints;
127 GetComponents(Constraints);
128 for (UPhysicsConstraintComponent* Constraint : Constraints)
129 {
130 if (!ConstraintsComponents.Contains(Constraint))
131 {
132 // Closed doors are welded to the chassis. A joint between two such
133 // components would constrain a Chaos particle to itself.
134 Constraint->TermComponentConstraint();
135 UPrimitiveComponent* CollisionDisabledComponent1 = Cast<UPrimitiveComponent>(
136 GetDefaultSubobjectByName(Constraint->ComponentName1.ComponentName));
137 UPrimitiveComponent* CollisionDisabledComponent2 = Cast<UPrimitiveComponent>(
138 GetDefaultSubobjectByName(Constraint->ComponentName2.ComponentName));
139 if (CollisionDisabledComponent1)
140 {
141 CollisionDisableConstraints.Add(CollisionDisabledComponent1, Constraint);
142 }
143 if (CollisionDisabledComponent2)
144 {
145 CollisionDisableConstraints.Add(CollisionDisabledComponent2, Constraint);
146 }
147 }
148 }
150
151 UChaosWheeledVehicleMovementComponent* MovementComponent = GetChaosWheeledVehicleMovementComponent();
152
153 if (MovementComponent)
154 {
155 check(MovementComponent != nullptr);
156
157 // UE5 Chaos defaults the steering input to SquaredFunction, an
158 // analog-gamepad feel curve that maps a steer command s to ~s^2 of full
159 // lock. CARLA clients (Traffic Manager, agents, manual control) expect
160 // the classic linear mapping from command to wheel angle; under the
161 // squared curve a 0.1 command yields ~1% of full lock and controllers
162 // tuned for a linear response cannot corner.
163 MovementComponent->SteeringInputRate.InputCurveFunction = EInputFunctionType::LinearFunction;
164
165 // Set the friction scale to Wheel CDO and update wheel setups
166 TArray<FChaosWheelSetup> NewWheelSetups = MovementComponent->WheelSetups;
167 for (const FChaosWheelSetup& WheelSetup : NewWheelSetups)
168 {
169 UChaosVehicleWheel* Wheel = WheelSetup.WheelClass.GetDefaultObject();
170 check(Wheel != nullptr);
171 }
172
173 MovementComponent->WheelSetups = NewWheelSetups;
174
176
177 // Update physics in the Ackermann Controller
179 }
181}
182
183// Head beams need far more light than the other vehicle groups: under this
184// project's fixed day-calibrated exposure, a visible pool on the road at
185// 10-20 m takes millions of lumens (street lamps run tens of millions), and
186// the beams' authored group intensity is ~30. Blinkers/brakes/fog/plate are
187// close-range signal lights whose authored values read fine and must NOT get
188// this factor (a multi-million-lumen blinker would strobe the whole street).
189static TAutoConsoleVariable<float> CVarCarlaVehicleBeamIntensityScale(
190 TEXT("carla.Light.VehicleBeamIntensityScale"),
191 100000.0f,
192 TEXT("Multiplier converting the authored UE4-era intensity of vehicle low/high beam ")
193 TEXT("light components (and the blueprint's 'Beam Lights' group variable) to UE5 ")
194 TEXT("photometric units. Set 1 to leave them untouched."),
195 ECVF_Default);
196
198{
199 // Vehicle light components ship bAutoActivate=false; UE4 rendered inactive
200 // lights anyway, UE 5.8 culls them outright, so every beam was invisible
201 // regardless of intensity. Activate only -- unlike street lamps, the
202 // authored automotive IES profiles (Vee/DefinedSpot beam shapes), shadow
203 // flags and attenuation radii are correct as-is and must not go through
204 // UCarlaLight::ActivateAndConfigureLightComponents (a 70 m radius floor on
205 // a blinker or license-plate light would be absurd).
206 //
207 // The base vehicle blueprint assigns each light component's intensity from
208 // per-group variables in its CONSTRUCTION script (so that has already
209 // happened by BeginPlay) and afterwards only toggles component VISIBILITY
210 // per light-state flag -- so converting the beam components right here
211 // sticks. The blueprint's "Beam Lights" group variable is scaled by the
212 // same factor so any later blueprint re-push writes the same converted
213 // value instead of reverting the beams to authored UE4 units.
214 const float BeamScale = CVarCarlaVehicleBeamIntensityScale.GetValueOnGameThread();
215 TArray<ULightComponent*> LightComponents;
216 GetComponents<ULightComponent>(LightComponents);
217 for (ULightComponent* LightComponent : LightComponents)
218 {
219 if (!LightComponent->IsActive())
220 {
221 LightComponent->SetActive(true);
222 }
223 const FString Name = LightComponent->GetName();
224 if (BeamScale != 1.0f &&
225 (Name.Contains(TEXT("low_beam")) || Name.Contains(TEXT("high_beam"))) &&
226 LightComponent->Intensity > 0.0f && LightComponent->Intensity < 1000.0f)
227 {
228 LightComponent->SetIntensity(LightComponent->Intensity * BeamScale);
229 }
230 }
231
232 if (BeamScale != 1.0f)
233 {
234 for (TFieldIterator<FFloatProperty> PropIt(GetClass()); PropIt; ++PropIt)
235 {
236 FFloatProperty* Property = *PropIt;
237 if (Property->GetAuthoredName() != TEXT("Beam Lights"))
238 {
239 continue;
240 }
241 float* ValuePtr = Property->ContainerPtrToValuePtr<float>(this);
242 if (ValuePtr != nullptr && *ValuePtr > 0.0f && *ValuePtr < 1000.0f)
243 {
244 *ValuePtr *= BeamScale;
245 UE_LOG(LogCarla, Verbose, TEXT("%s: scaled blueprint 'Beam Lights' to %f"),
246 *GetName(), *ValuePtr);
247 }
248 break;
249 }
250 }
251
252 // Light Defaults tool integration: everything above is this project's
253 // pre-existing native pipeline (per-vehicle authored intensities from the
254 // Blueprint's own Construction Script, scaled once here) -- it never
255 // touched Config/Lights/Defaults.json, so nothing edited/saved in the
256 // Vehicles tab ever reached an actual running simulation (manual_control.py
257 // et al), only the editor-only Preview spawn.
258 //
259 // Load the saved default once here (cheap, cached for the vehicle's
260 // lifetime) and apply it for the CURRENT light state -- at this point that
261 // is InputControl.LightState's default (everything off), so this must
262 // zero every group's material emissive, not light everything up. Do NOT
263 // use ApplyVehicleLightsToSingleActor/ApplyVehicleLightsLive here: those
264 // push Intensity uniformly to every exposed group at once, which is
265 // correct for the editor Preview (meant to show everything lit for
266 // editing) but wrong for gameplay, where a group must only glow when it is
267 // actually ON.
268 {
269 const FString ClassName = GetClass()->GetName();
272 }
274}
275
277{
279 return;
280
281 const FVehicleLightState& LightState = InputControl.LightState;
282 TMap<FString, bool> GroupOn;
283 GroupOn.Add(TEXT("Position"), LightState.Position);
284 GroupOn.Add(TEXT("Low Beam"), LightState.LowBeam);
285 GroupOn.Add(TEXT("High Beam"), LightState.HighBeam);
286 GroupOn.Add(TEXT("Fog"), LightState.Fog);
287 GroupOn.Add(TEXT("Brake"), LightState.Brake);
288 GroupOn.Add(TEXT("Reverse"), LightState.Reverse);
289 GroupOn.Add(TEXT("Left Blinker"), LightState.LeftBlinker);
290 GroupOn.Add(TEXT("Right Blinker"), LightState.RightBlinker);
291 GroupOn.Add(TEXT("Special1"), LightState.Special1);
292 GroupOn.Add(TEXT("Interior"), LightState.Interior);
293
294 TMap<FString, float> MaterialGroupValue;
295 for (const TPair<FString, bool>& Pair : GroupOn)
296 {
297 MaterialGroupValue.Add(Pair.Key, Pair.Value ? SavedVehicleLightIntensity : 0.0f);
298 }
299
301 this, MaterialGroupValue, SavedVehicleLightGroupIntensity);
302
303 // Authored modular lamps can opt into state control without relying on a
304 // template Blueprint's fixed component list. Untagged lights retain their
305 // existing Blueprint behavior and each tagged lamp retains its photometry.
306 TInlineComponentArray<ULightComponent*> AuthoredLights(this);
307 for (ULightComponent* Light : AuthoredLights)
308 {
309 bool bControlled = false;
310 bool bEnabled = false;
311 for (const TPair<FString, bool>& Pair : GroupOn)
312 {
313 if (Light->ComponentHasTag(FName(*(FString(TEXT("Carla.Light.")) + Pair.Key))))
314 {
315 bControlled = true;
316 bEnabled |= Pair.Value;
317 }
318 }
319 if (bControlled)
320 Light->SetVisibility(bEnabled);
321 }
322}
323
325{
327 {
328 return;
329 }
331
332 if (!IsTwoWheeledVehicle())
333 {
334 return;
335 }
336
337 RiderMeshComponent = Cast<USkeletalMeshComponent>(GetDefaultSubobjectByName(RiderMeshComponentName));
338 VehicleMeshForRiderSeat = GetMesh();
339
340 if ((RiderMeshComponent == nullptr) || (VehicleMeshForRiderSeat == nullptr) ||
342 {
343 UE_LOG(LogCarla, Warning,
344 TEXT("%s: IsTwoWheeledVehicle is true but rider component '%s' or seat socket '%s' could not be resolved -- rider seat-lock disabled."),
345 *GetName(), *RiderMeshComponentName.ToString(), *VehicleMeshSeatSocketName.ToString());
346 RiderMeshComponent = nullptr;
347 return;
348 }
349
350 // RiderMeshComponent ships parented (attached) to the vehicle mesh in
351 // the Blueprint's own component hierarchy -- the engine's normal
352 // attachment propagation tries to keep it glued to the parent's
353 // transform on its own, continuously, independent of and in addition to
354 // our own SetWorldTransform below. That's a second system moving the
355 // same component: our smoothed value can only "win" until the next
356 // native attachment update runs (driven by the parent's raw,
357 // unsmoothed motion), which is invisible to any log of what WE compute
358 // since it happens outside our own code entirely. Detaching once here
359 // (KeepWorldTransform: no visual pop) makes our own positioning the
360 // ONLY thing moving this component from this point on.
361 RiderMeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
362
363 // Doing the seat-lock from TickActor (tried first) still drifted while
364 // moving: the rider's own animation gets evaluated in a later phase of
365 // the frame than Actor tick, so whatever the Blueprint's per-tick logic
366 // (or the animation itself) does to the rider after TickActor returns
367 // still lands after our correction. OnBoneTransformsFinalized fires
368 // once this component's animation/bone evaluation is fully done for
369 // the frame -- there's nothing left afterwards to undo it before render.
370 //
371 // Neither this, a real engine attachment, nor forcing the rider to tick
372 // in TG_PostPhysics (all tried in this same investigation) fixed the
373 // drift -- the last one measurably made it worse, which rules out tick
374 // order as the cause (forcing later ticking should help or be neutral,
375 // not hurt). Async Physics is confirmed disabled project-wide too, so
376 // it isn't a render-interpolation gap either. The Riding/Stopped state
377 // machine's transition rules are also ruled out: Vehicle Speed <=/>
378 // Stop Speed with no dead zone, but Stop Speed is a near-zero threshold
379 // -- at the highway speeds where the drift is worst this never flips,
380 // so the state machine isn't blending anything there. Keeping this
381 // version (proven at least not to make things worse) as the baseline
382 // while OnRiderBoneTransformsFinalized below logs both sides of the
383 // seat-lock every frame to actually measure where the divergence comes
384 // from, instead of guessing at another candidate.
385 RiderMeshComponent->RegisterOnBoneTransformsFinalizedDelegate(
386 FOnBoneTransformsFinalizedMultiCast::FDelegate::CreateUObject(
388}
389
391{
392 if ((RiderMeshComponent == nullptr) || (VehicleMeshForRiderSeat == nullptr))
393 {
394 return;
395 }
396 // Logging both sides of the seat-lock every frame (temporary, since
397 // removed) proved the actual cause: the rider's own Anim Blueprint reads
398 // vehicle-mesh sockets (Seat, then confirmed the same on the
399 // handler/pedal sockets driving arm/leg IK once the seat was fixed and
400 // hands/feet were still popping) in ITS Event BlueprintUpdateAnimation,
401 // and on frames where that races ahead of the vehicle mesh's physics
402 // update for the frame, it reads exactly last frame's transform instead
403 // -- an intermittent one-frame-stale read (some frames correct, some
404 // not), which is what a visible pop/teleport looks like, not a
405 // continuous drift. Forcing this component's own tick group later
406 // (tried and reverted) didn't help because the Blueprint's read happens
407 // from its own animation update dispatch, not from this component's
408 // TickComponent.
409 // Fix: cache every socket's transform here (this delegate reliably
410 // fires late enough in the frame to always be correct, confirmed by the
411 // same log) and have the Blueprint read
412 // GetCachedVehicleSocketWorldTransform(SocketName) instead of querying
413 // the socket itself, for every seat/handler/pedal target in its Data
414 // Gather. That traded the intermittent race for a one-frame lag that's
415 // constant instead of flickering -- GetCachedVehicleSocketWorldTransform
416 // extrapolates it forward using the velocity and timestamp cached here
417 // (shared across sockets: they're all rigidly attached to the same
418 // moving mesh) to cancel out the remaining visible trailing-behind-at-speed
419 // too.
421 for (const FName& SocketName : VehicleMeshForRiderSeat->GetAllSocketNames())
422 {
423 CachedVehicleSocketTransforms.Add(SocketName, VehicleMeshForRiderSeat->GetSocketTransform(SocketName));
424 }
425 // LeftPedalGeo/RightPedalGeo are bones, not sockets (not covered by
426 // GetAllSocketNames above) -- GetSocketTransform falls back to a bone
427 // lookup when given a bone name, so this still works. Needed as pivots
428 // for the pedal-socket special case in GetCachedVehicleSocketWorldTransform().
429 CachedVehicleSocketTransforms.Add(TEXT("LeftPedalGeo"), VehicleMeshForRiderSeat->GetSocketTransform(TEXT("LeftPedalGeo")));
430 CachedVehicleSocketTransforms.Add(TEXT("RightPedalGeo"), VehicleMeshForRiderSeat->GetSocketTransform(TEXT("RightPedalGeo")));
431 CachedVehicleMeshTransform = VehicleMeshForRiderSeat->GetComponentTransform();
432 CachedVehicleWorldVelocity = VehicleMeshForRiderSeat->GetComponentVelocity();
434 FMath::RadiansToDegrees(CarlaGetActorAngularVelocityInRadians(*this)) :
435 VehicleMeshForRiderSeat->GetPhysicsAngularVelocityInDegrees();
436 if (const UWorld* World = GetWorld())
437 {
438 CachedVehicleTransformsTimeSeconds = World->GetTimeSeconds();
439 }
440
441 // See RiderSeatSmoothingSpeed's comment -- Chaos suspension has small,
442 // real, high-frequency vertical jitter on the mesh every frame; this
443 // low-pass filters CachedVehicleMeshTransform (used as the extrapolation
444 // base in GetCachedVehicleSocketWorldTransform()) so the rider doesn't
445 // inherit it 1:1. First frame snaps straight to the real transform --
446 // interpolating FROM a default-constructed FTransform (identity, at the
447 // world origin) would sweep the rider there for one frame.
448 //
449 // Only the vertical (Z) component of TRANSLATION is filtered -- X/Y
450 // (forward/lateral motion) are copied through exactly, every frame,
451 // with zero lag. Smoothing all of it (tried first) reintroduced a rider
452 // slide: FMath::VInterpTo always trails a moving target, and that lag
453 // grows with how fast the target (the vehicle) is moving -- at real
454 // driving speed it was very visibly behind, indistinguishable from the
455 // original slide bug. Suspension bounce is a small, purely vertical
456 // wobble, so filtering only Z bounds the worst-case lag to that
457 // wobble's own (small) amplitude, independent of vehicle speed.
458 //
459 // Smoothing Z alone didn't kill the visible bounce (confirmed: lowering
460 // RiderSeatSmoothingSpeed changed nothing about it once settled, only
461 // the initial snap-in speed) -- the real source is ROTATION jitter
462 // (small Pitch/Roll noise from the same suspension), never smoothed
463 // above for the same reason X/Y aren't: Yaw must track the handlebars
464 // instantly or turning gets laggy. But every socket (Seat, handlers,
465 // pedals) sits some distance from the mesh's own origin, and rotating
466 // that lever arm by a jittery Pitch/Roll every frame moves its WORLD
467 // position by an amount that grows with the lever arm -- invisible in
468 // pure translation-Z terms but very visible as the whole rider
469 // shuddering together, uniformly, exactly what was reported. Fix:
470 // filter Pitch and Roll the same way as Z, but decompose through
471 // Euler/FRotator instead of a straight FQuat::Slerp specifically to
472 // keep Yaw untouched (Slerp-ing the whole quaternion would smooth Yaw
473 // too and reintroduce steering lag, the same mistake already made once
474 // with translation).
476 {
479 }
480 else if (const UWorld* World = GetWorld())
481 {
482 const float FrameDeltaTime = World->GetDeltaSeconds();
483
484 FVector SmoothedTranslation = CachedVehicleMeshTransform.GetTranslation();
485 SmoothedTranslation.Z = FMath::FInterpTo(
486 SmoothedVehicleMeshTransform.GetTranslation().Z, SmoothedTranslation.Z,
487 FrameDeltaTime, RiderSeatSmoothingSpeed);
488 SmoothedVehicleMeshTransform.SetTranslation(SmoothedTranslation);
489
490 const FRotator PreviousSmoothedRotator = SmoothedVehicleMeshTransform.Rotator();
491 const FRotator RawRotator = CachedVehicleMeshTransform.Rotator();
492 const FRotator NewSmoothedRotator(
493 FMath::FInterpTo(PreviousSmoothedRotator.Pitch, RawRotator.Pitch, FrameDeltaTime, RiderSeatSmoothingSpeed),
494 RawRotator.Yaw,
495 FMath::FInterpTo(PreviousSmoothedRotator.Roll, RawRotator.Roll, FrameDeltaTime, RiderSeatSmoothingSpeed));
496 SmoothedVehicleMeshTransform.SetRotation(NewSmoothedRotator.Quaternion());
497 }
498
499 // Reads back through GetCachedVehicleSocketWorldTransform() (the
500 // now-smoothed pipeline above) instead of querying the seat socket
501 // directly -- this hard-snaps the rider's whole root every frame, so an
502 // unsmoothed read here would still show the suspension jitter on the
503 // rider's hips/torso regardless of how smooth the AnimGraph's own IK
504 // reads are.
505 const FTransform SeatWorldTransform = GetCachedVehicleSocketWorldTransform(VehicleMeshSeatSocketName);
506 const FTransform TargetTransform = FTransform(RiderSeatRelativeRotationOffset) * SeatWorldTransform;
507 RiderMeshComponent->SetWorldTransform(TargetTransform);
508}
509
510void ACarlaWheeledVehicle::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction){
511 Super::TickActor(DeltaTime, TickType, ThisTickFunction);
512
513 // Calipers follow the wheel centre and steering, but never wheel spin.
514 // Modular authoring opts in; existing vehicle animation is unchanged.
515 if (ActorHasTag(TEXT("Carla.ModularWheelCalipers")))
516 {
517 UChaosWheeledVehicleMovementComponent* Movement = GetChaosWheeledVehicleMovementComponent();
518 if (Movement && Movement->PhysicsVehicleOutput())
519 {
520 static const FName CaliperTags[] = {TEXT("Carla.Caliper.0"), TEXT("Carla.Caliper.1"),
521 TEXT("Carla.Caliper.2"), TEXT("Carla.Caliper.3")};
522 TInlineComponentArray<UStaticMeshComponent*> Components(this);
523 for (UStaticMeshComponent* Component : Components)
524 {
525 for (int32 Index = 0; Index < UE_ARRAY_COUNT(CaliperTags) && Index < Movement->WheelSetups.Num() && Index < Movement->Wheels.Num(); ++Index)
526 {
527 if (Component->ComponentHasTag(CaliperTags[Index]))
528 {
529 const FQuat Rotation = GetMesh()->GetComponentQuat() *
530 FRotator(0.0f, Movement->Wheels[Index]->GetSteerAngle(), 0.0f).Quaternion();
531 Component->SetWorldLocationAndRotation(
532 GetMesh()->GetSocketLocation(Movement->WheelSetups[Index].BoneName), Rotation);
533 break;
534 }
535 }
536 }
537 }
538 }
539
540 // Two-wheeled vehicles: resolves (once) the rider components and
541 // attaches the rider to the seat socket -- see ResolveRiderComponentsIfNeeded's
542 // comment for why a real attachment, not a per-frame transform copy.
544
545 // See GetPedalRotation()'s comment: a continuously-accumulated angle
546 // instead of the vehicle mesh's looping Timeline, so the pedal bone
547 // (and the rider's foot IK riding on it) never pops.
549 {
550 PedalRotationAngle = FMath::Fmod(
552 360.0f);
553 }
554
555 // When velocity/acceleration control is active, flush control every frame even without AI controller
556 if (VelocityControl->IsActive() || AccelerationControl->IsActive())
557 {
559 }
560
561 FPoseSnapshot pose;
562 GetMesh()->SnapshotPose(pose);
563 MakePoseWorldSpace(GetMesh(), pose);
564
566
567}
568
569bool ACarlaWheeledVehicle::IsInVehicleRange(const FVector& Location) const
570{
571 TRACE_CPUPROFILER_EVENT_SCOPE(ACarlaWheeledVehicle::IsInVehicleRange);
572
573 return FoliageBoundingBox.IsInside(Location);
574}
575
577{
578 const FTransform GlobalTransform = GetActorTransform();
579 const FVector Vec{ DetectionSize, DetectionSize, DetectionSize };
580 FBox Box = FBox(-Vec, Vec);
581 const FTransform NonScaledTransform(GlobalTransform.GetRotation(), GlobalTransform.GetLocation(), { 1.0f, 1.0f, 1.0f });
582 FoliageBoundingBox = Box.TransformBy(NonScaledTransform);
583}
584
585const TArray<int32> ACarlaWheeledVehicle::GetFoliageInstancesCloseToVehicle(const UInstancedStaticMeshComponent* Component) const
586{
587 TRACE_CPUPROFILER_EVENT_SCOPE(ACarlaWheeledVehicle::GetFoliageInstancesCloseToVehicle);
588 return Component->GetInstancesOverlappingBox(FoliageBoundingBox);
589}
590
592{
593 TRACE_CPUPROFILER_EVENT_SCOPE(ACarlaWheeledVehicle::GetDetectionBox);
594 return FoliageBoundingBox;
595}
596
598{
599 return DetectionSize;
600}
601
603{
604 const FVector& Center = FoliageBoundingBox.GetCenter();
605 const FVector& Extent = FoliageBoundingBox.GetExtent();
606 const FQuat& Rotation = GetActorQuat();
607 DrawDebugBox(GetWorld(), Center, Extent, Rotation, FColor::Magenta, false, 0.0f, 0, 5.0f);
608}
609
611{
613 if (LargeMap)
614 {
615 FTransform GlobalTransform = LargeMap->LocalToGlobalTransform(GetActorTransform());
616 return VehicleBounds->CalcBounds(GlobalTransform);
617 }
618 return VehicleBounds->CalcBounds(GetActorTransform());
619}
620
622{
624
625 const FTransform& CompToWorldTransform = RootComponent->GetComponentTransform();
626 const FRotator Rotation = CompToWorldTransform.GetRotation().Rotator();
627 const FVector Translation = CompToWorldTransform.GetLocation();
628 const FVector Scale = CompToWorldTransform.GetScale3D();
629
630 // Invert BB origin to local space
631 BoundingBox.Origin -= Translation;
632 BoundingBox.Origin = Rotation.UnrotateVector(BoundingBox.Origin);
633 BoundingBox.Origin /= Scale;
634
635 // Prepare Box Collisions
636 FTransform Transform;
637 Transform.SetTranslation(BoundingBox.Origin);
638 VehicleBounds->SetRelativeTransform(Transform);
639 VehicleBounds->SetBoxExtent(BoundingBox.Extent);
640}
641
642// =============================================================================
643// -- Get functions ------------------------------------------------------------
644// =============================================================================
645
654
656{
657 return GetVehicleTransform().GetRotation().GetForwardVector();
658}
659
661{
663 {
664 return MirroredGear;
665 }
666 // Acceleration control (the Autoware command path) sets the forward speed
667 // directly and never engages the transmission, which stays in neutral (0).
668 // It only drives forward, so report the first forward gear.
669 if (AccelerationControl->IsActive())
670 {
671 return 1;
672 }
674}
675
677{
678 return VehicleBounds->GetRelativeTransform();
679}
680
682{
683 return VehicleBounds->GetScaledBoxExtent();
684}
685
687{
688 UChaosWheeledVehicleMovementComponent* MovementComponent = GetChaosWheeledVehicleMovementComponent();
689 // The runtime wheel holds the applied physics control, the class the defaults.
690 if (MovementComponent != nullptr && MovementComponent->Wheels.Num() > 0 &&
691 MovementComponent->Wheels[0] != nullptr)
692 {
693 return MovementComponent->Wheels[0]->MaxSteerAngle;
694 }
695 if (MovementComponent != nullptr && MovementComponent->WheelSetups.Num() > 0 &&
696 MovementComponent->WheelSetups[0].WheelClass != nullptr)
697 {
698 const UChaosVehicleWheel* FrontWheel =
699 Cast<UChaosVehicleWheel>(MovementComponent->WheelSetups[0].WheelClass->GetDefaultObject());
700 if (FrontWheel != nullptr)
701 {
702 return FrontWheel->MaxSteerAngle;
703 }
704 }
705 // A vehicle whose movement component has no wheel setups (e.g. a rig whose
706 // configuration did not survive the UE5 migration) must not assert the
707 // whole server down from BeginPlay; report a sensible steering default.
708 UE_LOG(LogCarla, Warning, TEXT("%s: no wheel setups, returning default max steer angle"), *GetName());
709 return 35.0f;
710}
711
712// =============================================================================
713// -- Set functions ------------------------------------------------------------
714// =============================================================================
715
717{
721 }
722 // A Multi-GPU mirror's idle drivetrain reports gear 0: it keeps the
723 // primary's, replayed with the control.
724 const int32 ReplayedGear = InputControl.Control.Gear;
727 {
728 InputControl.Control.Gear = ReplayedGear;
729 }
730 InputControl.Control.bReverse = InputControl.Control.Gear < 0;
733}
734
736{
738 Control.Throttle = Value;
739 ApplyVehicleControl(Control, EVehicleInputPriority::User);
740}
741
743{
745 Control.Steer = Value;
746 ApplyVehicleControl(Control, EVehicleInputPriority::User);
747}
748
750{
752 Control.Brake = Value;
753 ApplyVehicleControl(Control, EVehicleInputPriority::User);
754}
755
757{
759 Control.bReverse = Value;
760 ApplyVehicleControl(Control, EVehicleInputPriority::User);
761}
762
764{
766 Control.bHandBrake = Value;
767 ApplyVehicleControl(Control, EVehicleInputPriority::User);
768}
769
771{
772 TArray<float> WheelsFrictionScale;
773 UChaosWheeledVehicleMovementComponent* Movement = GetChaosWheeledVehicleMovementComponent();
774 if (Movement)
775 {
776 check(Movement != nullptr);
777
778 for (auto& Wheel : Movement->Wheels)
779 {
780 WheelsFrictionScale.Add(Wheel->FrictionForceMultiplier);
781 }
782 }
783
784 return WheelsFrictionScale;
785}
786
787void ACarlaWheeledVehicle::SetWheelsFrictionScale(TArray<float>& WheelsFrictionScale)
788{
789 UChaosWheeledVehicleMovementComponent* Movement = GetChaosWheeledVehicleMovementComponent();
790 if (Movement)
791 {
792 // The runtime wheel array is empty until the Chaos vehicle finishes its
793 // physics initialization: don't crash on early or mismatched requests.
794 const int32 Count = FMath::Min(Movement->Wheels.Num(), WheelsFrictionScale.Num());
795 for (int32 i = 0; i < Count; ++i)
796 {
797 Movement->SetWheelFrictionMultiplier(i, WheelsFrictionScale[i]);
798 Movement->Wheels[i]->FrictionForceMultiplier = WheelsFrictionScale[i];
799 if (LastPhysicsControl.Wheels.IsValidIndex(i))
800 {
801 LastPhysicsControl.Wheels[i].FrictionForceMultiplier = WheelsFrictionScale[i];
802 }
803 }
804 }
805}
806
808{
810 auto VehicleMovComponentPtr = GetChaosWheeledVehicleMovementComponent();
811 check(VehicleMovComponentPtr != nullptr);
812 auto& VehicleMovComponent = *VehicleMovComponentPtr;
813 auto& EngineSetup = VehicleMovComponent.EngineSetup;
814 auto RCurve = EngineSetup.TorqueCurve.GetRichCurve();
815 check(RCurve != nullptr);
816 PhysicsControl.TorqueCurve = *RCurve;
817 PhysicsControl.MaxTorque = EngineSetup.MaxTorque;
818 PhysicsControl.MaxRPM = EngineSetup.MaxRPM;
819 PhysicsControl.IdleRPM = EngineSetup.EngineIdleRPM;
820 PhysicsControl.BrakeEffect = EngineSetup.EngineBrakeEffect;
821 PhysicsControl.RevUpMOI = EngineSetup.EngineRevUpMOI;
822 PhysicsControl.RevDownRate = EngineSetup.EngineRevDownRate;
823 auto& DifferentialSetup = VehicleMovComponent.DifferentialSetup;
824 PhysicsControl.DifferentialType = (uint8_t)DifferentialSetup.DifferentialType;
825 PhysicsControl.FrontRearSplit = DifferentialSetup.FrontRearSplit;
826 auto& TransmissionSetup = VehicleMovComponent.TransmissionSetup;
827 PhysicsControl.bUseAutomaticGears = TransmissionSetup.bUseAutomaticGears;
828 PhysicsControl.GearChangeTime = TransmissionSetup.GearChangeTime;
829 PhysicsControl.FinalRatio = TransmissionSetup.FinalRatio;
830 PhysicsControl.ForwardGearRatios = TransmissionSetup.ForwardGearRatios;
831 PhysicsControl.ReverseGearRatios = TransmissionSetup.ReverseGearRatios;
832 PhysicsControl.ChangeUpRPM = TransmissionSetup.ChangeUpRPM;
833 PhysicsControl.ChangeDownRPM = TransmissionSetup.ChangeDownRPM;
834 PhysicsControl.TransmissionEfficiency = TransmissionSetup.TransmissionEfficiency;
835 PhysicsControl.Mass = VehicleMovComponent.Mass;
836 PhysicsControl.DragCoefficient = VehicleMovComponent.DragCoefficient;
837 PhysicsControl.CenterOfMass = GetCenterOfMass(VehicleMovComponent);
838 PhysicsControl.ChassisWidth = VehicleMovComponent.ChassisWidth;
839 PhysicsControl.ChassisHeight = VehicleMovComponent.ChassisHeight;
840 PhysicsControl.DownforceCoefficient = VehicleMovComponent.DownforceCoefficient;
841 PhysicsControl.DragArea = VehicleMovComponent.DragArea;
842 PhysicsControl.InertiaTensorScale = VehicleMovComponent.InertiaTensorScale;
843 PhysicsControl.SleepThreshold = VehicleMovComponent.SleepThreshold;
844 PhysicsControl.SleepSlopeLimit = VehicleMovComponent.SleepSlopeLimit;
845 RCurve = VehicleMovComponent.SteeringSetup.SteeringCurve.GetRichCurve();
846 check(RCurve != nullptr);
847 PhysicsControl.SteeringCurve = *RCurve;
848 PhysicsControl.UseSweepWheelCollision = false;
849 // Wheels Setup. The runtime wheel instances (Wheels) are only created once
850 // the Chaos vehicle finishes its physics initialization, which happens after
851 // the spawn frame: a client asking for the physics control right after
852 // spawning would index an empty array (and crash the server). Fall back to
853 // each wheel class' defaults until the runtime instances exist.
854 PhysicsControl.Wheels.SetNum(VehicleMovComponent.WheelSetups.Num());
855 for (int32 i = 0; i < PhysicsControl.Wheels.Num(); ++i)
856 {
857 const UChaosVehicleWheel* InPtr = nullptr;
858 if (VehicleMovComponent.Wheels.IsValidIndex(i))
859 {
860 InPtr = VehicleMovComponent.Wheels[i];
861 }
862 else if (auto WheelClass = VehicleMovComponent.WheelSetups[i].WheelClass)
863 {
864 InPtr = WheelClass->GetDefaultObject<UChaosVehicleWheel>();
865 }
866 if (InPtr == nullptr)
867 {
868 continue;
869 }
870 auto& In = *InPtr;
871 auto& Out = PhysicsControl.Wheels[i];
872 Out.AxleType = In.AxleType;
873 Out.Offset = In.Offset;
874 Out.WheelRadius = In.WheelRadius;
875 Out.WheelWidth = In.WheelWidth;
876 Out.WheelMass = In.WheelMass;
877 Out.CorneringStiffness = In.CorneringStiffness;
878 Out.FrictionForceMultiplier = In.FrictionForceMultiplier;
879 Out.SideSlipModifier = In.SideSlipModifier;
880 Out.SlipThreshold = In.SlipThreshold;
881 Out.SkidThreshold = In.SkidThreshold;
882 Out.MaxSteerAngle = In.MaxSteerAngle;
883 Out.bAffectedBySteering = In.bAffectedBySteering;
884 Out.bAffectedByBrake = In.bAffectedByBrake;
885 Out.bAffectedByHandbrake = In.bAffectedByHandbrake;
886 Out.bAffectedByEngine = In.bAffectedByEngine;
887 Out.bABSEnabled = In.bABSEnabled;
888 Out.bTractionControlEnabled = In.bTractionControlEnabled;
889 Out.MaxWheelspinRotation = In.MaxWheelspinRotation;
890 Out.ExternalTorqueCombineMethod = In.ExternalTorqueCombineMethod;
891 auto SlipCurve = In.LateralSlipGraph.GetRichCurveConst();
892 check(SlipCurve != nullptr);
893 Out.LateralSlipGraph = *SlipCurve;
894 Out.SuspensionAxis = In.SuspensionAxis;
895 Out.SuspensionForceOffset = In.SuspensionForceOffset;
896 Out.SuspensionMaxRaise = In.SuspensionMaxRaise;
897 Out.SuspensionMaxDrop = In.SuspensionMaxDrop;
898 Out.SuspensionDampingRatio = In.SuspensionDampingRatio;
899 Out.WheelLoadRatio = In.WheelLoadRatio;
900 Out.SpringRate = In.SpringRate;
901 Out.SpringPreload = In.SpringPreload;
902 Out.SuspensionSmoothing = In.SuspensionSmoothing;
903 Out.RollbarScaling = In.RollbarScaling;
904 Out.SweepShape = In.SweepShape;
905 Out.SweepType = In.SweepType;
906 Out.MaxBrakeTorque = In.MaxBrakeTorque;
907 Out.MaxHandBrakeTorque = In.MaxHandBrakeTorque;
908 Out.WheelIndex = In.WheelIndex;
909 Out.Location = In.Location;
910 Out.OldLocation = In.OldLocation;
911 Out.Velocity = In.Velocity;
912 // If some of the wheels use Spherecast, it will be used for everyone
913 // if we call ApplyPhisicsControl() as it has no sense to have it only for one.
914 if (In.SweepShape == ESweepShape::Spherecast)
915 PhysicsControl.UseSweepWheelCollision = true;
916 }
917 return PhysicsControl;
918}
919
920FVector ACarlaWheeledVehicle::GetCenterOfMass(UChaosWheeledVehicleMovementComponent& VehicleMovComponent) const
921{
922 if (VehicleMovComponent.bEnableCenterOfMassOverride)
923 {
924 return VehicleMovComponent.CenterOfMassOverride;
925 }
926 else {
927 auto PrimitiveComponentPtr = Cast<UPrimitiveComponent>(VehicleMovComponent.UpdatedComponent);
928 check(PrimitiveComponentPtr != nullptr);
929 auto& PrimitiveComponent = *PrimitiveComponentPtr;
930 return PrimitiveComponent.BodyInstance.COMNudge;
931 }
932}
933
938
943
946{
948 auto VehicleMovComponentPtr = GetChaosWheeledVehicleMovementComponent();
949 check(VehicleMovComponentPtr != nullptr);
950 auto& VehicleMovComponent = *VehicleMovComponentPtr;
951 auto& EngineSetup = VehicleMovComponent.EngineSetup;
952 EngineSetup.TorqueCurve.EditorCurveData = PhysicsControl.TorqueCurve;
953 EngineSetup.MaxTorque = PhysicsControl.MaxTorque;
954 EngineSetup.MaxRPM = PhysicsControl.MaxRPM;
955 EngineSetup.EngineIdleRPM = PhysicsControl.IdleRPM;
956 EngineSetup.EngineBrakeEffect = PhysicsControl.BrakeEffect;
957 EngineSetup.EngineRevUpMOI = PhysicsControl.RevUpMOI;
958 EngineSetup.EngineRevDownRate = PhysicsControl.RevDownRate;
959 auto& DifferentialSetup = VehicleMovComponent.DifferentialSetup;
960 DifferentialSetup.DifferentialType = (EVehicleDifferential)PhysicsControl.DifferentialType;
961 DifferentialSetup.FrontRearSplit = PhysicsControl.FrontRearSplit;
962 auto& TransmissionSetup = VehicleMovComponent.TransmissionSetup;
963 TransmissionSetup.bUseAutomaticGears = PhysicsControl.bUseAutomaticGears;
964 TransmissionSetup.GearChangeTime = PhysicsControl.GearChangeTime;
965 TransmissionSetup.FinalRatio = PhysicsControl.FinalRatio;
966 TransmissionSetup.ForwardGearRatios = PhysicsControl.ForwardGearRatios;
967 TransmissionSetup.ReverseGearRatios = PhysicsControl.ReverseGearRatios;
968 TransmissionSetup.ChangeUpRPM = PhysicsControl.ChangeUpRPM;
969 TransmissionSetup.ChangeDownRPM = PhysicsControl.ChangeDownRPM;
970 TransmissionSetup.TransmissionEfficiency = PhysicsControl.TransmissionEfficiency;
971 VehicleMovComponent.Mass = PhysicsControl.Mass;
972 VehicleMovComponent.DragCoefficient = PhysicsControl.DragCoefficient;
973 SetCenterOfMass(VehicleMovComponent, PhysicsControl);
974 VehicleMovComponent.ChassisWidth = PhysicsControl.ChassisWidth;
975 VehicleMovComponent.ChassisHeight = PhysicsControl.ChassisHeight;
976 VehicleMovComponent.DownforceCoefficient = PhysicsControl.DownforceCoefficient;
977 VehicleMovComponent.DragArea = PhysicsControl.DragArea;
978 VehicleMovComponent.InertiaTensorScale = PhysicsControl.InertiaTensorScale;
979 VehicleMovComponent.SleepThreshold = PhysicsControl.SleepThreshold;
980 VehicleMovComponent.SleepSlopeLimit = PhysicsControl.SleepSlopeLimit;
981 auto& SteeringSetup = VehicleMovComponent.SteeringSetup;
982 SteeringSetup.SteeringCurve.EditorCurveData = PhysicsControl.SteeringCurve;
983
984 // Recreate first: it rebuilds the wheels from the wheel class defaults.
985 VehicleMovComponent.RecreatePhysicsState();
988
989 auto* Recorder = UCarlaStatics::GetRecorder(GetWorld());
990 if (Recorder && Recorder->IsEnabled())
991 {
992 Recorder->AddPhysicsControl(*this);
993 }
994
995 // Update physics in the Ackermann Controller
997}
998
1000{
1001 UChaosWheeledVehicleMovementComponent* Movement = GetChaosWheeledVehicleMovementComponent();
1002 if (Movement == nullptr)
1003 {
1004 return;
1005 }
1006 const TArray<FWheelPhysicsControl>& WheelControls = LastPhysicsControl.Wheels;
1007 const int32 WheelCount = WheelControls.Num();
1008 if (WheelCount == 0)
1009 {
1010 return;
1011 }
1012 if (WheelCount != Movement->WheelSetups.Num())
1013 {
1014 UE_LOG(LogCarla, Error,
1015 TEXT("%s: %d WheelPhysicsControl for %d wheels, skipping wheel setup"),
1016 *GetName(), WheelCount, Movement->WheelSetups.Num());
1017 return;
1018 }
1019 // The runtime wheel instances only exist once the Chaos vehicle finishes its
1020 // physics initialization (after the spawn frame): applying wheel physics
1021 // before that would index an empty array and crash the server.
1022 if (Movement->Wheels.Num() < WheelCount)
1023 {
1024 UE_LOG(LogCarla, Warning,
1025 TEXT("ApplyVehiclePhysicsControl: wheels not initialized yet on %s, skipping wheel setup"),
1026 *GetName());
1027 return;
1028 }
1029
1030 for (int32 i = 0; i < WheelCount; ++i)
1031 {
1032 const FWheelPhysicsControl& In = WheelControls[i];
1033 UChaosVehicleWheel* OutPtr = Movement->Wheels[i];
1034 check(OutPtr != nullptr);
1035 UChaosVehicleWheel& Out = *OutPtr;
1036
1037 Movement->SetWheelRadius(i, In.WheelRadius);
1038 Movement->SetWheelFrictionMultiplier(i, In.FrictionForceMultiplier);
1039 Movement->SetWheelMaxSteerAngle(i, In.MaxSteerAngle);
1040 Movement->SetWheelMaxBrakeTorque(i, In.MaxBrakeTorque);
1041 Movement->SetWheelHandbrakeTorque(i, In.MaxHandBrakeTorque);
1042 Movement->SetABSEnabled(i, In.bABSEnabled);
1043 Movement->SetTractionControlEnabled(i, In.bTractionControlEnabled);
1044 Movement->SetAffectedByBrake(i, In.bAffectedByBrake);
1045 Movement->SetAffectedByHandbrake(i, In.bAffectedByHandbrake);
1046 Movement->SetAffectedBySteering(i, In.bAffectedBySteering);
1047 // On a front or rear axle the differential decides which wheels are
1048 // driven: only let the control take power away from one of them.
1049 if (!In.bAffectedByEngine || Out.AxleType == EAxleType::Undefined)
1050 {
1051 Movement->SetAffectedByEngine(i, In.bAffectedByEngine);
1052 }
1053 Movement->SetTorqueCombineMethod(In.ExternalTorqueCombineMethod, i);
1054
1055 // get_physics_control reads these back, and the wheel trace reads the
1056 // sweep settings from the wheel object every tick.
1057 Out.WheelRadius = In.WheelRadius;
1058 Out.FrictionForceMultiplier = In.FrictionForceMultiplier;
1059 Out.MaxSteerAngle = In.MaxSteerAngle;
1060 Out.MaxBrakeTorque = In.MaxBrakeTorque;
1061 Out.MaxHandBrakeTorque = In.MaxHandBrakeTorque;
1062 Out.bABSEnabled = In.bABSEnabled;
1063 Out.bTractionControlEnabled = In.bTractionControlEnabled;
1064 Out.bAffectedByBrake = In.bAffectedByBrake;
1065 Out.bAffectedByHandbrake = In.bAffectedByHandbrake;
1066 Out.bAffectedBySteering = In.bAffectedBySteering;
1067 Out.bAffectedByEngine = In.bAffectedByEngine;
1068 Out.ExternalTorqueCombineMethod = In.ExternalTorqueCombineMethod;
1069 Out.SweepShape =
1071 ESweepShape::Spherecast :
1072 ESweepShape::Raycast;
1073 Out.SweepType = In.SweepType;
1074 }
1075}
1076
1077void ACarlaWheeledVehicle::SetCenterOfMass(UChaosWheeledVehicleMovementComponent& VehicleMovComponent, const FVehiclePhysicsControl& PhysicsControl)
1078{
1079 if (VehicleMovComponent.bEnableCenterOfMassOverride)
1080 {
1081 VehicleMovComponent.CenterOfMassOverride = PhysicsControl.CenterOfMass;
1082 }
1083 else {
1084 auto PrimitiveComponentPtr = Cast<UPrimitiveComponent>(VehicleMovComponent.UpdatedComponent);
1085 check(PrimitiveComponentPtr != nullptr);
1086 auto& PrimitiveComponent = *PrimitiveComponentPtr;
1087 PrimitiveComponent.BodyInstance.COMNudge = PhysicsControl.CenterOfMass;
1088 }
1089}
1090
1092{
1095 }
1097
1098 if (InputControl.Priority <= Priority)
1099 {
1100 InputControl.Control = Control;
1101 InputControl.Priority = Priority;
1102 }
1103}
1104
1111
1113{
1114 VelocityControl->Activate(Velocity);
1115}
1116
1121
1123{
1124 AccelerationControl->Activate(Acceleration);
1125}
1126
1131
1132void ACarlaWheeledVehicle::ApplyVehicleAccelerationControl(float LongitudinalAccelerationMps2, float Steer, float SteerSpeed)
1133{
1135 {
1137 }
1139 VelocityControl->Deactivate();
1140
1141 // Longitudinal: acceleration from control_cmd [m/s^2] -> Unreal uses cm/s^2
1142 const FVector AccelerationCmps2(LongitudinalAccelerationMps2 * 100.0f, 0.0f, 0.0f);
1143 AccelerationControl->Activate(AccelerationCmps2);
1144
1145 InputControl.Control.Steer = Steer;
1146 InputControl.Priority = EVehicleInputPriority::User;
1147}
1148
1150{
1151 FVehicleTelemetryData TelemetryData{};
1152
1153 auto *VehicleMovComponent = GetChaosWheeledVehicleMovementComponent();
1154 check(VehicleMovComponent != nullptr);
1155
1156 // Vehicle-level telemetry. Forward speed is reported by the engine in cm/s
1157 // and converted to m/s. Aerodynamic drag is intentionally not exposed: the
1158 // Chaos plugin's DebugDragMagnitude is declared but never written, so any
1159 // value reported here would be a permanent zero.
1160 TelemetryData.Speed = GetVehicleForwardSpeed() / 100.0f;
1161 TelemetryData.Steer = LastAppliedControl.Steer;
1162 TelemetryData.Throttle = LastAppliedControl.Throttle;
1163 TelemetryData.Brake = LastAppliedControl.Brake;
1164 TelemetryData.EngineRPM = VehicleMovComponent->GetEngineRotationSpeed();
1165 TelemetryData.Gear = GetVehicleCurrentGear();
1166
1167 // Per-wheel telemetry. UChaosWheeledVehicleMovementComponent::GetWheelState
1168 // returns live solver slip data; UChaosVehicleWheel::GetWheelAngularVelocity
1169 // is also live. The legacy PhysX surface (tire load, torque, long/lat force)
1170 // has no Chaos equivalent that is populated in stock UE 5.5 (the Debug*
1171 // UPROPERTYs on UChaosVehicleWheel are never written by the plugin), so it
1172 // is omitted here rather than reported as silent zeros.
1173 const int32 WheelCount = VehicleMovComponent->Wheels.Num();
1174 TelemetryData.Wheels.Reserve(WheelCount);
1175 for (int32 i = 0; i < WheelCount; ++i)
1176 {
1177 FWheelTelemetryData WheelData{};
1178 // VehicleMovComponent->Wheels is TArray<TObjectPtr<UChaosVehicleWheel>>;
1179 // bind to a reference so TObjectPtr's implicit conversion handles the
1180 // dereferences below (raw `auto *` does not deduce through TObjectPtr).
1181 const auto &Wheel = VehicleMovComponent->Wheels[i];
1182 if (!Wheel)
1183 {
1184 TelemetryData.Wheels.Add(WheelData);
1185 continue;
1186 }
1187
1188 const FWheelStatus &WheelStatus = VehicleMovComponent->GetWheelState(i);
1189
1190 WheelData.LatSlip = FMath::RadiansToDegrees(WheelStatus.SlipAngle);
1191 WheelData.LongSlip = WheelStatus.SlipMagnitude;
1192 WheelData.Omega = Wheel->GetWheelAngularVelocity();
1193
1194 TelemetryData.Wheels.Add(WheelData);
1195 }
1196
1197 return TelemetryData;
1198}
1199
1201{
1202 if (GetWorld()->GetFirstPlayerController())
1203 {
1204 ACarlaHUD* hud = Cast<ACarlaHUD>(GetWorld()->GetFirstPlayerController()->GetHUD());
1205 if (hud) {
1206
1207 // Set/Unset the car movement component in HUD to show the temetry
1208 if (Enabled) {
1210 }
1211 else{
1213 hud->AddDebugVehicleForTelemetry(nullptr);
1214 //GetChaosWheeledVehicleMovementComponent()->StopTelemetry();
1215 }
1216 }
1217
1218 }
1219 else {
1220 UE_LOG(LogCarla, Warning, TEXT("ACarlaWheeledVehicle::ShowDebugTelemetry:: Cannot find HUD for debug info"));
1221 }
1222 }
1223}
1224
1226{
1227 if (LightState.Position != InputControl.LightState.Position ||
1228 LightState.LowBeam != InputControl.LightState.LowBeam ||
1229 LightState.HighBeam != InputControl.LightState.HighBeam ||
1230 LightState.Brake != InputControl.LightState.Brake ||
1231 LightState.RightBlinker != InputControl.LightState.RightBlinker ||
1232 LightState.LeftBlinker != InputControl.LightState.LeftBlinker ||
1233 LightState.Reverse != InputControl.LightState.Reverse ||
1234 LightState.Fog != InputControl.LightState.Fog ||
1235 LightState.Interior != InputControl.LightState.Interior ||
1236 LightState.Special1 != InputControl.LightState.Special1 ||
1237 LightState.Special2 != InputControl.LightState.Special2)
1238 {
1239 InputControl.LightState = LightState;
1240 // The blueprint reads its per-group intensity variables (already scaled
1241 // once at spawn in ActivateVehicleLightComponents) and re-pushes them
1242 // from a looping timer, so no per-call component rescale is needed here
1243 // -- it would race that timer anyway.
1245 // RefreshLightState's own graph is opaque to C++ and may repush its own
1246 // (weak, authored) emissive values for whichever groups just toggled --
1247 // reapply the Light Defaults saved values after it so ours win.
1249 }
1250}
1251
1253{
1254 FailureState = InFailureState;
1255}
1256
1258{
1260 {
1261 BaseMovementComponent->DestroyComponent();
1262 }
1263 BaseMovementComponent = NewBaseMovementComponent;
1264}
1265
1266// The pose the animation blueprint shows for a wheel, or the rest pose if the
1267// blueprint does not derive from UCarlaVehicleAnimationInstance.
1268static FWheelAnimationData GetShownWheelPose(const USkeletalMeshComponent &Mesh, int32 WheelIndex)
1269{
1270 const auto *VehicleAnim = Cast<UCarlaVehicleAnimationInstance>(Mesh.GetAnimInstance());
1271 const FWheelAnimationData *Pose = VehicleAnim != nullptr ? VehicleAnim->GetWheelPose(WheelIndex) : nullptr;
1272 return Pose != nullptr ? *Pose : FWheelAnimationData{NAME_None, FRotator::ZeroRotator, FVector::ZeroVector};
1273}
1274
1276{
1277 if (FWheelAnimationData *Pose = GetOverriddenWheelPoseOrWarn(WheelLocation, TEXT("SetWheelSteerDirection")))
1278 {
1279 Pose->RotOffset.Yaw = AngleInDeg;
1280 }
1281}
1282
1284{
1285 // EVehicleWheelLocation is the index into the movement component's wheel
1286 // arrays: UChaosVehicleWheel::WheelIndex is documented as "our index in the
1287 // vehicle's (and setup's) wheels array".
1288 const int32 WheelIndex = static_cast<int32>(WheelLocation);
1289 return HasWheelOrWarn(WheelIndex, TEXT("GetWheelSteerAngle")) ? GetWheelPose(WheelIndex).RotOffset.Yaw : 0.0F;
1290}
1291
1293{
1294 if (FWheelAnimationData *Pose = GetOverriddenWheelPoseOrWarn(WheelLocation, TEXT("SetWheelPitchAngle")))
1295 {
1296 Pose->RotOffset.Pitch = AngleInDeg;
1297 }
1298}
1299
1301{
1302 const int32 WheelIndex = static_cast<int32>(WheelLocation);
1303 return HasWheelOrWarn(WheelIndex, TEXT("GetWheelPitchAngle")) ? GetWheelPose(WheelIndex).RotOffset.Pitch : 0.0F;
1304}
1305
1307{
1308 if (FWheelAnimationData *Pose = GetOverriddenWheelPoseOrWarn(WheelLocation, TEXT("SetWheelSuspensionOffset")))
1309 {
1310 // The same offset Chaos applies.
1311 Pose->LocOffset = -GetWheelSuspensionAxis(static_cast<int32>(WheelLocation)) * OffsetInCm;
1312 }
1313}
1314
1316{
1317 const int32 WheelIndex = static_cast<int32>(WheelLocation);
1318 if (!HasWheelOrWarn(WheelIndex, TEXT("GetWheelSuspensionOffset")))
1319 {
1320 return 0.0F;
1321 }
1322 return -FVector::DotProduct(GetWheelPose(WheelIndex).LocOffset, GetWheelSuspensionAxis(WheelIndex));
1323}
1324
1326{
1327 if (bEnabled && !HasCarlaVehicleAnimation())
1328 {
1329 UE_LOG(LogCarla, Warning,
1330 TEXT("SetWheelAnimationOverride: the animation blueprint of %s does not derive from CarlaVehicleAnimationInstance."),
1331 *GetName());
1332 return;
1333 }
1334 if (bEnabled && !bWheelAnimationOverridden)
1335 {
1336 // Start from the latest pose, so the wheels do not jump.
1338 }
1339 else if (!bEnabled)
1340 {
1342 {
1344 }
1345 OverriddenWheelPoses.Reset();
1346 }
1347 bWheelAnimationOverridden = bEnabled;
1348}
1349
1351 EVehicleWheelLocation WheelLocation,
1352 const TCHAR *Caller)
1353{
1354 const int32 WheelIndex = static_cast<int32>(WheelLocation);
1355 if (!HasWheelOrWarn(WheelIndex, Caller))
1356 {
1357 return nullptr;
1358 }
1360 {
1361 UE_LOG(LogCarla, Warning,
1362 TEXT("%s: the wheel animation override is off on %s; enable it first."),
1363 Caller, *GetName());
1364 return nullptr;
1365 }
1366 return OverriddenWheelPoses.IsValidIndex(WheelIndex) ? &OverriddenWheelPoses[WheelIndex] : nullptr;
1367}
1368
1369FWheelAnimationData ACarlaWheeledVehicle::GetWheelPose(int32 WheelIndex) const
1370{
1371 if (bWheelAnimationOverridden && OverriddenWheelPoses.IsValidIndex(WheelIndex))
1372 {
1373 return OverriddenWheelPoses[WheelIndex];
1374 }
1375 if (const UChaosVehicleWheel *Wheel = GetSimulatedWheel(WheelIndex))
1376 {
1377 // The pose Chaos draws, in degrees and cm.
1378 const FRotator Rotation(Wheel->GetRotationAngle(), Wheel->GetSteerAngle(), 0.0f);
1379 const FVector Offset = -GetWheelSuspensionAxis(WheelIndex) * Wheel->GetSuspensionOffset();
1380 return FWheelAnimationData{NAME_None, Rotation, Offset};
1381 }
1382 return GetShownWheelPose(*GetMesh(), WheelIndex);
1383}
1384
1385FWheelAnimationData ACarlaWheeledVehicle::GetDrawnWheelPose(int32 WheelIndex) const
1386{
1387 const auto *VehicleAnim = Cast<UCarlaVehicleAnimationInstance>(GetMesh()->GetAnimInstance());
1388 if (const FWheelAnimationData *Pose = VehicleAnim != nullptr ? VehicleAnim->GetWheelPose(WheelIndex) : nullptr)
1389 {
1390 return *Pose;
1391 }
1392 return GetWheelPose(WheelIndex);
1393}
1394
1395TArray<FWheelAnimationData> ACarlaWheeledVehicle::GetWheelPoses() const
1396{
1397 TArray<FWheelAnimationData> Poses;
1398 for (int32 i = 0; HasWheel(i); ++i)
1399 {
1400 Poses.Add(GetWheelPose(i));
1401 }
1402 return Poses;
1403}
1404
1405void ACarlaWheeledVehicle::SeedChaosWheels(const TArray<FWheelAnimationData> &Poses)
1406{
1407 UChaosWheeledVehicleMovementComponent *Movement = GetChaosWheeledVehicleMovementComponent();
1408 if (Movement == nullptr)
1409 {
1410 return;
1411 }
1412 // SetSnapshot dereferences the physics vehicle, created with this output.
1413 TUniquePtr<FPhysicsVehicleOutput> &Output = Movement->PhysicsVehicleOutput();
1414 if (!Output.IsValid() || Output->Wheels.Num() != Poses.Num())
1415 {
1416 return;
1417 }
1418 FWheeledSnaphotData Snapshot;
1419 Movement->GetBaseSnapshot(Snapshot);
1420 TArray<float> Spins;
1421 const float ForwardSpeed = FVector::DotProduct(Snapshot.LinearVelocity, GetActorForwardVector());
1422 for (int32 i = 0; i < Poses.Num(); ++i)
1423 {
1424 // Chaos recomputes steering and suspension every step; only the spin carries over.
1425 const FWheelAnimationData &Pose = Poses[i];
1426 const float Radius = GetWheelRadius(i);
1427 FWheelSnapshot &Wheel = Snapshot.WheelSnapshots.AddDefaulted_GetRef();
1428 Wheel.WheelRotationAngle = Pose.RotOffset.Pitch;
1429 Wheel.SteeringAngle = Pose.RotOffset.Yaw;
1430 Wheel.SuspensionOffset = -FVector::DotProduct(Pose.LocOffset, GetWheelSuspensionAxis(i));
1431 Wheel.WheelRadius = Radius;
1432 Wheel.WheelAngularVelocity = Radius > UE_KINDA_SMALL_NUMBER ? ForwardSpeed / Radius : 0.0f;
1433 // What is drawn until the next physics step, which a sleeping vehicle skips.
1434 Output->Wheels[i].AngularPosition = FMath::DegreesToRadians(-Wheel.WheelRotationAngle);
1435 Spins.Add(Wheel.WheelRotationAngle);
1436 }
1437 Movement->SetSnapshot(Snapshot);
1438 ArmSeedRewrite(MoveTemp(Spins));
1439}
1440
1442{
1444 FPhysScene *Scene = GetWorld() != nullptr ? GetWorld()->GetPhysicsScene() : nullptr;
1445 if (Scene == nullptr)
1446 {
1447 return;
1448 }
1449 PendingSeedSpins = MoveTemp(Spins);
1450 bSeedStepStarted = false;
1451 SeedPreTickHandle = Scene->OnPhysScenePreTick.AddUObject(this, &ACarlaWheeledVehicle::OnSeedPreTick);
1452 SeedPostTickHandle = Scene->OnPhysScenePostTick.AddUObject(this, &ACarlaWheeledVehicle::OnSeedPostTick);
1453}
1454
1456{
1457 FPhysScene *Scene = GetWorld() != nullptr ? GetWorld()->GetPhysicsScene() : nullptr;
1458 if (Scene != nullptr)
1459 {
1460 Scene->OnPhysScenePreTick.Remove(SeedPreTickHandle);
1461 Scene->OnPhysScenePostTick.Remove(SeedPostTickHandle);
1462 }
1463 SeedPreTickHandle.Reset();
1464 SeedPostTickHandle.Reset();
1465 PendingSeedSpins.Reset();
1466}
1467
1468void ACarlaWheeledVehicle::OnSeedPreTick(FPhysScene_Chaos *Scene, float DeltaSeconds)
1469{
1470 // This step is the first to start from the seeded wheels.
1471 bSeedStepStarted = true;
1472}
1473
1475{
1476 if (!bSeedStepStarted)
1477 {
1478 return;
1479 }
1480 UChaosWheeledVehicleMovementComponent *Movement = GetChaosWheeledVehicleMovementComponent();
1481 if (Movement != nullptr)
1482 {
1483 TUniquePtr<FPhysicsVehicleOutput> &Output = Movement->PhysicsVehicleOutput();
1484 if (Output.IsValid() && Output->Wheels.Num() == PendingSeedSpins.Num())
1485 {
1486 for (int32 i = 0; i < PendingSeedSpins.Num(); ++i)
1487 {
1488 Output->Wheels[i].AngularPosition = FMath::DegreesToRadians(-PendingSeedSpins[i]);
1489 }
1490 }
1491 }
1493}
1494
1495bool ACarlaWheeledVehicle::HasWheelOrWarn(int32 WheelIndex, const TCHAR *Caller) const
1496{
1497 if (!HasWheel(WheelIndex))
1498 {
1499 // Reachable from the client, a bike asked for BL_Wheel say, so it must not
1500 // bring the server down.
1501 UE_LOG(LogCarla, Warning,
1502 TEXT("%s: wheel location %d does not exist on %s."),
1503 Caller, WheelIndex, *GetName());
1504 return false;
1505 }
1506 return true;
1507}
1508
1509bool ACarlaWheeledVehicle::HasWheel(int32 WheelIndex) const
1510{
1511 const UChaosWheeledVehicleMovementComponent *Movement = GetChaosWheeledVehicleMovementComponent();
1512 return Movement != nullptr && Movement->WheelSetups.IsValidIndex(WheelIndex);
1513}
1514
1516{
1517 return Cast<UCarlaVehicleAnimationInstance>(GetMesh()->GetAnimInstance()) != nullptr;
1518}
1519
1520const UChaosVehicleWheel *ACarlaWheeledVehicle::GetSimulatedWheel(int32 WheelIndex) const
1521{
1522 // Non-const: the engine does not mark PhysicsVehicleOutput() const.
1523 UChaosWheeledVehicleMovementComponent *Movement = GetChaosWheeledVehicleMovementComponent();
1524 if (!IsSimulatedByChaos() || Movement == nullptr ||
1525 !Movement->Wheels.IsValidIndex(WheelIndex))
1526 {
1527 return nullptr;
1528 }
1529 // The wheel getters read the async solver's output and check() that it is
1530 // there; it only exists once the physics state has been created and stepped.
1531 const UChaosVehicleWheel *Wheel = Movement->Wheels[WheelIndex];
1532 const TUniquePtr<FPhysicsVehicleOutput> &VehicleOutput = Movement->PhysicsVehicleOutput();
1533 if (Wheel == nullptr || !VehicleOutput.IsValid() ||
1534 !VehicleOutput->Wheels.IsValidIndex(Wheel->WheelIndex))
1535 {
1536 return nullptr;
1537 }
1538 return Wheel;
1539}
1540
1546
1547float ACarlaWheeledVehicle::GetWheelRadius(int32 WheelIndex) const
1548{
1549 if (LastPhysicsControl.Wheels.IsValidIndex(WheelIndex))
1550 {
1551 return LastPhysicsControl.Wheels[WheelIndex].WheelRadius;
1552 }
1553 const UChaosVehicleWheel *Wheel = GetWheelDefaults(WheelIndex);
1554 return Wheel != nullptr ? Wheel->WheelRadius : 0.0f;
1555}
1556
1558{
1559 if (LastPhysicsControl.Wheels.IsValidIndex(WheelIndex))
1560 {
1561 return LastPhysicsControl.Wheels[WheelIndex].SuspensionAxis.GetSafeNormal();
1562 }
1563 const UChaosVehicleWheel *Wheel = GetWheelDefaults(WheelIndex);
1564 return Wheel != nullptr ? Wheel->SuspensionAxis.GetSafeNormal() : FVector::DownVector;
1565}
1566
1567const UChaosVehicleWheel *ACarlaWheeledVehicle::GetWheelDefaults(int32 WheelIndex) const
1568{
1569 // For when a rejected physics control left fewer entries than wheels.
1570 const UChaosWheeledVehicleMovementComponent *Movement = GetChaosWheeledVehicleMovementComponent();
1571 if (Movement == nullptr || !Movement->WheelSetups.IsValidIndex(WheelIndex))
1572 {
1573 return nullptr;
1574 }
1575 return Movement->WheelSetups[WheelIndex].WheelClass.GetDefaultObject();
1576}
1577
1579 if (!GetCarlaMovementComponent<UDefaultMovementComponent>())
1580 {
1581 return;
1582 }
1583
1584 UChaosWheeledVehicleMovementComponent* Movement = GetChaosWheeledVehicleMovementComponent();
1585 if (Movement)
1586 {
1587 check(Movement != nullptr);
1588
1589 if (bPhysicsEnabled == enabled)
1590 return;
1591
1592 // Rebuilding the physics state resets the poses drawn.
1593 const TArray<FWheelAnimationData> ShownPoses = GetWheelPoses();
1594
1595 SetActorEnableCollision(true);
1596 UPrimitiveComponent* RootPrimitive =
1597 Cast<UPrimitiveComponent>(GetRootComponent());
1598 RootPrimitive->SetSimulatePhysics(enabled);
1599 RootPrimitive->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
1600
1601 if (enabled)
1602 {
1603 Movement->SetUpdatedComponent(GetMesh());
1604 Movement->RecreatePhysicsState();
1606 // The recreated body starts asleep and Chaos vehicle input does not
1607 // wake it, leaving the drivetrain permanently inert (full throttle,
1608 // zero motion) until something else wakes the body. Wake it here.
1609 RootPrimitive->WakeAllRigidBodies();
1610 SeedChaosWheels(ShownPoses);
1611 }
1612 else
1613 {
1614 Movement->DestroyPhysicsState();
1615 }
1616 bPhysicsEnabled = enabled;
1617
1619 }
1620}
1621
1623{
1624 for (int i = 0; i < ConstraintsComponents.Num(); i++)
1625 {
1627 }
1628 for (int i = 0; i < ConstraintsComponents.Num(); i++)
1629 {
1631 }
1632}
1633
1635{
1637 {
1638 return MirroredVelocity;
1639 }
1641}
1642
1643void ACarlaWheeledVehicle::SetMirroredMotion(const FVector &Velocity, float ForwardSpeed)
1644{
1645 bHasMirroredMotion = true;
1646 MirroredVelocity = Velocity;
1647 MirroredForwardSpeed = ForwardSpeed;
1648}
1649
1651{
1652 bHasMirroredMotion = false;
1653 MirroredVelocity = FVector::ZeroVector;
1654 MirroredForwardSpeed = 0.0f;
1655}
1656
1657void ACarlaWheeledVehicle::EndPlay(const EEndPlayReason::Type EndPlayReason)
1658{
1659 //ShowDebugTelemetry(false);
1661 Super::EndPlay(EndPlayReason);
1663}
1664
1666 if (int(DoorIdx) >= ConstraintsComponents.Num() && DoorIdx != EVehicleDoor::All) {
1667 UE_LOG(LogTemp, Warning, TEXT("This door is not configured for this car."));
1668 return;
1669 }
1670
1671 if (DoorIdx == EVehicleDoor::All) {
1672 for (int i = 0; i < ConstraintsComponents.Num(); i++)
1673 {
1675 }
1676 return;
1677 }
1678
1679 OpenDoorPhys(DoorIdx);
1680}
1681
1683 if (int(DoorIdx) >= ConstraintsComponents.Num() && DoorIdx != EVehicleDoor::All) {
1684 UE_LOG(LogTemp, Warning, TEXT("This door is not configured for this car."));
1685 return;
1686 }
1687
1688 if (DoorIdx == EVehicleDoor::All) {
1689 for (int i = 0; i < ConstraintsComponents.Num(); i++)
1690 {
1692 }
1693 return;
1694 }
1695
1696 CloseDoorPhys(DoorIdx);
1697}
1698
1700{
1701 UPhysicsConstraintComponent* Constraint = ConstraintsComponents[static_cast<int>(DoorIdx)];
1702 UPrimitiveComponent* DoorComponent = ConstraintDoor[Constraint];
1703 DoorComponent->DetachFromComponent(
1704 FDetachmentTransformRules(EDetachmentRule::KeepWorld, false));
1705 FTransform DoorInitialTransform =
1706 DoorComponentsTransform[DoorComponent] * GetActorTransform();
1707 DoorComponent->SetWorldTransform(DoorInitialTransform);
1708 DoorComponent->SetSimulatePhysics(true);
1709 DoorComponent->SetCollisionProfileName(TEXT("BlockAll"));
1710 float AngleLimit = Constraint->ConstraintInstance.GetAngularSwing1Limit();
1711 FRotator AngularRotationOffset = Constraint->ConstraintInstance.AngularRotationOffset;
1712
1713 if (Constraint->ConstraintInstance.AngularRotationOffset.Yaw < 0.0f)
1714 {
1715 AngleLimit = -AngleLimit;
1716 }
1717 Constraint->SetAngularOrientationTarget(FRotator(0, AngleLimit, 0));
1718 Constraint->SetAngularDriveParams(DoorOpenStrength, 1.0, 0.0);
1719
1720 Constraint->InitComponentConstraint();
1721
1722 UPhysicsConstraintComponent** CollisionDisable =
1723 CollisionDisableConstraints.Find(DoorComponent);
1724 if (CollisionDisable)
1725 {
1726 (*CollisionDisable)->InitComponentConstraint();
1727 }
1728
1729 RecordDoorChange(DoorIdx, true);
1730}
1731
1733{
1734 UPhysicsConstraintComponent* Constraint = ConstraintsComponents[static_cast<int>(DoorIdx)];
1735 UPrimitiveComponent* DoorComponent = ConstraintDoor[Constraint];
1736 // Remove the joints before disabling the door body and welding it back
1737 // into the chassis. Otherwise Chaos can retain a self-referencing edge.
1738 Constraint->TermComponentConstraint();
1739 if (UPhysicsConstraintComponent** CollisionDisable = CollisionDisableConstraints.Find(DoorComponent))
1740 {
1741 (*CollisionDisable)->TermComponentConstraint();
1742 }
1743 FTransform DoorInitialTransform =
1744 DoorComponentsTransform[DoorComponent] * GetActorTransform();
1745 DoorComponent->SetSimulatePhysics(false);
1746 DoorComponent->SetCollisionProfileName(TEXT("NoCollision"));
1747 DoorComponent->SetWorldTransform(DoorInitialTransform);
1748 DoorComponent->AttachToComponent(
1749 GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepWorld, true));
1750 RecordDoorChange(DoorIdx, false);
1751}
1752
1753TArray<TPair<int32, FTransform>> ACarlaWheeledVehicle::GetOpenDoorPoses() const
1754{
1755 TArray<TPair<int32, FTransform>> Poses;
1756 const FTransform ActorInverseTransform = GetActorTransform().Inverse();
1757 for (int32 i = 0; i < ConstraintsComponents.Num(); ++i)
1758 {
1759 UPrimitiveComponent* const* Door = ConstraintDoor.Find(ConstraintsComponents[i]);
1760 // OpenDoorPhys detaches a door, CloseDoorPhys attaches it back.
1761 if ((Door != nullptr) && (*Door != nullptr) && ((*Door)->GetAttachParent() == nullptr))
1762 {
1763 Poses.Emplace(i, (*Door)->GetComponentTransform() * ActorInverseTransform);
1764 }
1765 }
1766 return Poses;
1767}
1768
1769void ACarlaWheeledVehicle::SetMirroredDoorPoses(const TArray<TPair<int32, FTransform>> &OpenDoors)
1770{
1771 const FTransform ActorTransform = GetActorTransform();
1772 for (int32 i = 0; i < ConstraintsComponents.Num(); ++i)
1773 {
1774 UPrimitiveComponent* const* DoorPtr = ConstraintDoor.Find(ConstraintsComponents[i]);
1775 UPrimitiveComponent* Door = (DoorPtr != nullptr) ? *DoorPtr : nullptr;
1776 if (Door == nullptr)
1777 {
1778 continue;
1779 }
1780 const TPair<int32, FTransform>* Open =
1781 OpenDoors.FindByPredicate([i](const TPair<int32, FTransform>& Pose) { return Pose.Key == i; });
1782 const bool bShownOpen = (Door->GetAttachParent() == nullptr);
1783 if (Open != nullptr)
1784 {
1785 if (!bShownOpen)
1786 {
1787 Door->DetachFromComponent(FDetachmentTransformRules(EDetachmentRule::KeepWorld, false));
1788 // What ray casts hit on the primary, where it is a physics body.
1789 Door->SetCollisionProfileName(TEXT("BlockAll"));
1790 }
1791 Door->SetWorldTransform(Open->Value * ActorTransform);
1792 }
1793 else if (bShownOpen)
1794 {
1795 // As CloseDoorPhys leaves it.
1796 Door->SetCollisionProfileName(TEXT("NoCollision"));
1797 Door->SetWorldTransform(DoorComponentsTransform.FindRef(Door) * ActorTransform);
1798 Door->AttachToComponent(GetMesh(), FAttachmentTransformRules(EAttachmentRule::KeepWorld, true));
1799 }
1800 }
1801}
1802
1804{
1805 auto* Recorder = UCarlaStatics::GetRecorder(GetWorld());
1806 if (Recorder && Recorder->IsEnabled())
1807 {
1808 Recorder->AddVehicleDoor(*this, DoorIdx, bIsOpen);
1809 }
1810}
1811
1813{
1814 auto roll = GetVehicleTransform().Rotator().Roll;
1815
1816 // The angular velocity reduction is applied in 4 stages, to improve its smoothness.
1817 // Case 4 starts the timer to set the rollover flag, so users are notified.
1818 switch (RolloverBehaviorTracker) {
1819 case 0: CheckRollover(roll, std::make_pair(130.0, 230.0)); break;
1820 case 1: CheckRollover(roll, std::make_pair(140.0, 220.0)); break;
1821 case 2: CheckRollover(roll, std::make_pair(150.0, 210.0)); break;
1822 case 3: CheckRollover(roll, std::make_pair(160.0, 200.0)); break;
1823 case 4:
1824 GetWorld()->GetTimerManager().SetTimer(TimerHandler, this, &ACarlaWheeledVehicle::SetRolloverFlag, RolloverFlagTime);
1826 break;
1827 case 5: break;
1828 default:
1830 }
1831
1832 // In case the vehicle recovers, reset the rollover tracker
1833 if (RolloverBehaviorTracker > 0 && -30 < roll && roll < 30) {
1836 }
1837}
1838
1839void ACarlaWheeledVehicle::CheckRollover(const float roll, const std::pair<float, float> threshold_roll) {
1840 if (threshold_roll.first < roll && roll < threshold_roll.second) {
1841 auto Root = Cast<UPrimitiveComponent>(GetRootComponent());
1842 auto AngularVelocity = Root->GetPhysicsAngularVelocityInDegrees();
1843 Root->SetPhysicsAngularVelocityInDegrees((1 - RolloverBehaviorForce) * AngularVelocity);
1845 }
1846}
1847
1849 // Make sure the vehicle hasn't recovered since the timer started
1850 if (RolloverBehaviorTracker >= 4) {
1852 }
1853}
1854
1858
1860{
1861 const UObject* World = GetWorld();
1862 TArray<AActor*> ActorsInLevel;
1863 UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), ActorsInLevel);
1864 for (AActor* Actor : ActorsInLevel)
1865 {
1866 AVegetationManager* Manager = Cast<AVegetationManager>(Actor);
1867 if (!IsValid(Manager))
1868 continue;
1869 Manager->AddVehicle(this);
1870 return;
1871 }
1872}
1873
1875{
1876 const UObject* World = GetWorld();
1877 TArray<AActor*> ActorsInLevel;
1878 UGameplayStatics::GetAllActorsOfClass(World, AActor::StaticClass(), ActorsInLevel);
1879 for (AActor* Actor : ActorsInLevel)
1880 {
1881 AVegetationManager* Manager = Cast<AVegetationManager>(Actor);
1882 if (!IsValid(Manager))
1883 continue;
1884 Manager->RemoveVehicle(this);
1885 return;
1886 }
1887}
1888
1890 UPhysicsConstraintComponent* Component)
1891{
1892 return Component->ConstraintInstance.AngularRotationOffset;
1893}
1894
1896 UPhysicsConstraintComponent* Component, const FRotator& NewAngle)
1897{
1898 Component->ConstraintInstance.AngularRotationOffset = NewAngle;
1899}
1900
1902{
1903 if(WorldTransformedPose.bIsValid == false)
1904 {
1905 SetActorTickEnabled(true);
1906 GetMesh()->SnapshotPose(WorldTransformedPose);
1908 }
1909 return WorldTransformedPose;
1910}
FVector CarlaGetActorAngularVelocityInRadians(const AActor &Actor)
Angular velocity of Actor in the world frame, in rad/s: on a Multi-GPU secondary the primary's (a mir...
static FWheelAnimationData GetShownWheelPose(const USkeletalMeshComponent &Mesh, int32 WheelIndex)
static TAutoConsoleVariable< float > CVarCarlaVehicleBeamIntensityScale(TEXT("carla.Light.VehicleBeamIntensityScale"), 100000.0f, TEXT("photometric units. Set 1 to leave them untouched."), ECVF_Default)
static void MakePoseWorldSpace(USkeletalMeshComponent *Mesh, FPoseSnapshot &Pose)
EVehicleWheelLocation
EVehicleDoor
Type of door to open/close.
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
EVehicleInputPriority
Class to draw on HUD.
Definition CarlaHUD.h:45
void AddDebugVehicleForTelemetry(UChaosWheeledVehicleMovementComponent *NewDebugVehiclePtr)
Definition CarlaHUD.h:63
UChaosWheeledVehicleMovementComponent * DebugVehicle
Definition CarlaHUD.h:61
FVector GetVehicleBoundingBoxExtent() const
Extent of the vehicle's bounding box.
TObjectPtr< UVehicleVelocityControl > VelocityControl
void SetWheelsFrictionScale(TArray< float > &WheelsFrictionScale)
FBoxSphereBounds GetBoxSphereBounds() const
void RecordDoorChange(const EVehicleDoor DoorIdx, const bool bIsOpen)
FVehicleLightState LightState
void ArmSeedRewrite(TArray< float > Spins)
The next physics pre-tick copies in the output of the step before the seed, so the seeded spins are w...
bool HasWheelOrWarn(int32 WheelIndex, const TCHAR *Caller) const
void CloseDoorPhys(const EVehicleDoor DoorIdx)
FDelegateHandle SeedPreTickHandle
float GetWheelRadius(int32 WheelIndex) const
Radius of the wheel at WheelIndex, in cm, as last applied through the physics control.
FVector CachedVehicleWorldAngularVelocityDegrees
FVehicleLightState GetVehicleLightState() const
void SetMirroredMotion(const FVector &Velocity, float ForwardSpeed)
Multi-GPU secondary: the primary's motion of the vehicle this one mirrors (world frame,...
void SetThrottleInput(float Value)
carla::rpc::VehicleFailureState GetFailureState() const
virtual void TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction &ThisTickFunction) override
void RefreshLightState(const FVehicleLightState &VehicleLightState)
FPoseSnapshot WorldTransformedPose
void SetWheelSuspensionOffset(EVehicleWheelLocation WheelLocation, float OffsetInCm)
Suspension compression of a wheel, in cm.
void ActivateAccelerationControl(const FVector &Acceleration)
TMap< UPrimitiveComponent *, UPhysicsConstraintComponent * > CollisionDisableConstraints
bool HasCarlaVehicleAnimation() const
Whether the animation blueprint derives from UCarlaVehicleAnimationInstance, which is what draws the ...
bool IsSimulatedByChaos() const
Whether the Chaos vehicle simulation is moving this vehicle.
const TArray< int32 > GetFoliageInstancesCloseToVehicle(const UInstancedStaticMeshComponent *Component) const
float GetWheelSteerAngle(EVehicleWheelLocation WheelLocation)
void ApplyVehicleAccelerationControl(float LongitudinalAccelerationMps2, float Steer, float SteerSpeed)
Apply control from Autoware /control/command/control_cmd (acceleration [m/s^2] + steering)
void SetCenterOfMass(UChaosWheeledVehicleMovementComponent &VehicleMovComponent, const FVehiclePhysicsControl &PhysicsControl)
bool IsInVehicleRange(const FVector &Location) const
void SetCarlaMovementComponent(UBaseCarlaMovementComponent *MoementComponent)
float GetWheelPitchAngle(EVehicleWheelLocation WheelLocation)
FVector GetVehicleOrientation() const
Orientation vector of the vehicle, pointing forward.
virtual void BeginPlay() override
float GetMaximumSteerAngle() const
Get the maximum angle at which the front wheel can steer.
EVehicleInputPriority Priority
void ApplyVehiclePhysicsControl(const FVehiclePhysicsControl &PhysicsControl)
FVehiclePhysicsControl GetVehiclePhysicsControl() const
TArray< float > GetWheelsFrictionScale()
FVehicleControl LastAppliedControl
void SetFailureState(const carla::rpc::VehicleFailureState &FailureState)
FVehicleTelemetryData GetVehicleTelemetryData() const
Returns a snapshot of the vehicle's runtime telemetry: forward speed, last applied control inputs,...
static void SetPhysicsConstraintAngle(UPhysicsConstraintComponent *Component, const FRotator &NewAngle)
void SetVehicleLightState(const FVehicleLightState &LightState)
void CheckRollover(const float roll, const std::pair< float, float > threshold_roll)
UChaosWheeledVehicleMovementComponent * GetChaosWheeledVehicleMovementComponent() const
Get chaos wheeled vehicle movement component casted.
TArray< UPhysicsConstraintComponent * > ConstraintsComponents
TObjectPtr< UVehicleAccelerationControl > AccelerationControl
int32 GetVehicleCurrentGear() const
Active gear of the vehicle.
TArray< FWheelAnimationData > OverriddenWheelPoses
FVehicleAckermannControl LastAppliedAckermannControl
void SeedChaosWheels(const TArray< FWheelAnimationData > &Poses)
Sets the Chaos wheels to these poses, so that they do not jump when Chaos takes over the wheels again...
FDelegateHandle SeedPostTickHandle
struct ACarlaWheeledVehicle::@1 InputControl
float RiderSeatSmoothingSpeed
Chaos suspension has small, real, high-frequency vertical jitter on the vehicle mesh every frame (vis...
void SetMirroredDoorPoses(const TArray< TPair< int32, FTransform > > &OpenDoors)
Multi-GPU secondary: shows these doors open, at these poses relative to the vehicle,...
FWheelAnimationData * GetOverriddenWheelPoseOrWarn(EVehicleWheelLocation WheelLocation, const TCHAR *Caller)
The overridden pose for a setter to write, or nullptr, with a warning, if it cannot be set.
FVehiclePhysicsControl LastPhysicsControl
void OnSeedPostTick(FChaosScene *Scene)
static FRotator GetPhysicsConstraintAngle(UPhysicsConstraintComponent *Component)
float GetWheelSuspensionOffset(EVehicleWheelLocation WheelLocation)
FWheelAnimationData GetWheelPose(int32 WheelIndex) const
The pose shown for a wheel: the overridden one, otherwise Chaos's, otherwise the one last drawn.
float GetVehicleForwardSpeed() const
Forward speed in cm/s. Might be negative if goes backwards.
TArray< float > PendingSeedSpins
Degrees, as drawn.
void SetWheelAnimationOverride(bool bEnabled)
While the wheel animation is overridden the wheels show only the values set through the wheel setters...
void SetBrakeInput(float Value)
FVector GetWheelSuspensionAxis(int32 WheelIndex) const
Unit vector, component space, along which the wheel's suspension drops.
void CloseDoor(const EVehicleDoor DoorIdx)
FRotator RiderSeatRelativeRotationOffset
The rider mesh's rest pose isn't authored to sit flush with the socket's own local axes โ€“ its compone...
TObjectPtr< UBoxComponent > VehicleBounds
void ActivateVehicleLightComponents()
Some vehicle Blueprints (Tesla, Mustang, DodgeCharger, ...) author real Spot/Point light components f...
TMap< UPhysicsConstraintComponent *, UPrimitiveComponent * > ConstraintDoor
void OnSeedPreTick(FPhysScene_Chaos *Scene, float DeltaSeconds)
void SetSimulatePhysics(bool enabled)
void ApplyVehicleAckermannControl(const FVehicleAckermannControl &AckermannControl, EVehicleInputPriority Priority)
TMap< UPrimitiveComponent *, FTransform > DoorComponentsTransform
TObjectPtr< USkeletalMeshComponent > RiderMeshComponent
virtual FVector GetVelocity() const override
void SetSteeringInput(float Value)
void ShowDebugTelemetry(bool Enabled)
TMap< FName, FTransform > CachedVehicleSocketTransforms
void ApplyRolloverBehavior()
Rollovers tend to have too much angular velocity, resulting in the vehicle doing a full 360ยบ flip.
UBaseCarlaMovementComponent * BaseMovementComponent
FTransform GetVehicleTransform() const
Transform of the vehicle.
TArray< FName > ConstraintComponentNames
FTransform GetCachedVehicleSocketWorldTransform(FName SocketName) const
Cached, extrapolated world transform of ANY socket on the vehicle mesh (Seat, HandlerLeftSocket,...
float PedalRotationDegreesPerCm
Degrees the pedal bone rotates per centimeter of real vehicle travel (see GetPedalRotation).
void ActivateVelocityControl(const FVector &Velocity)
void ApplyVehicleControl(const FVehicleControl &Control, EVehicleInputPriority Priority)
FWheelAnimationData GetDrawnWheelPose(int32 WheelIndex) const
Wheel pose the animation instance drew this frame (else GetWheelPose); what secondaries must draw.
TObjectPtr< USkeletalMeshComponent > VehicleMeshForRiderSeat
ACarlaWheeledVehicle(const FObjectInitializer &ObjectInitializer)
FVector GetCenterOfMass(UChaosWheeledVehicleMovementComponent &VehicleMovComponent) const
void OpenDoor(const EVehicleDoor DoorIdx)
carla::rpc::VehicleFailureState FailureState
FTransform SmoothedVehicleMeshTransform
void OpenDoorPhys(const EVehicleDoor DoorIdx)
void SetWheelSteerDirection(EVehicleWheelLocation WheelLocation, float AngleInDeg)
Set the rotation of the car wheels indicated by the user 0 = FL_VehicleWheel, 1 = FR_VehicleWheel,...
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason)
bool HasWheel(int32 WheelIndex) const
TArray< FWheelAnimationData > GetWheelPoses() const
GetWheelPose for every wheel.
FPoseSnapshot GetWorldTransformedPose()
FTransform GetVehicleBoundingBoxTransform() const
Transform of the vehicle's bounding box relative to the vehicle.
void ApplyWheelPhysicsControl()
Applies LastPhysicsControl.Wheels to the live Chaos wheels.
const UChaosVehicleWheel * GetWheelDefaults(int32 WheelIndex) const
void SetHandbrakeInput(bool Value)
const UChaosVehicleWheel * GetSimulatedWheel(int32 WheelIndex) const
The Chaos wheel, while Chaos simulates the vehicle and has output for it.
void SetWheelPitchAngle(EVehicleWheelLocation WheelLocation, float AngleInDeg)
Spin angle of a wheel, in degrees. Purely visual, wheel animation override only.
TMap< FString, float > SavedVehicleLightGroupIntensity
FAckermannController AckermannController
void PrintROS2Message(const char *Message)
FName RiderMeshComponentName
Two-wheeled vehicles (bikes/motorcycles) embed their rider as a SkeletalMeshComponent (named RiderMes...
TArray< TPair< int32, FTransform > > GetOpenDoorPoses() const
Every door open now (a physics body of its own while open), by door index, with its pose relative to ...
FTransform LocalToGlobalTransform(const FTransform &InTransform) const
void AddVehicle(ACarlaWheeledVehicle *Vehicle)
void RemoveVehicle(ACarlaWheeledVehicle *Vehicle)
void UpdateVehicleState(const ACarlaWheeledVehicle *Vehicle)
void RunLoop(FVehicleControl &Control)
void SetTargetPoint(const FVehicleAckermannControl &AckermannControl)
void UpdateVehiclePhysics(const ACarlaWheeledVehicle *Vehicle)
virtual void ProcessControl(FVehicleControl &Control)
static FBoundingBox GetVehicleBoundingBox(const ACarlaWheeledVehicle *Vehicle, uint8 InTagQueried=0xFF)
static ALargeMapManager * GetLargeMapManager(const UObject *WorldContextObject)
static ACarlaRecorder * GetRecorder(const UObject *WorldContextObject)
static void CreateDefaultMovementComponent(ACarlaWheeledVehicle *Vehicle)
static void ApplyVehicleLightsRuntimeState(AActor *Actor, const TMap< FString, float > &MaterialGroupValue, const TMap< FString, float > &RealLightGroupIntensity)
static bool LoadVehicleLightDefault(const FString &ClassName, UPARAM(ref) float &OutIntensity, UPARAM(ref) TMap< FString, float > &OutGroupIntensity)
ETorqueCombineMethod ExternalTorqueCombineMethod
Determines how the SetDriveTorque/SetBrakeTorque inputs are combined with the internal torques.
float MaxHandBrakeTorque
Max handbrake brake torque for this wheel (Nm).
ESweepType SweepType
Whether wheel suspension considers simple, complex.