CARLA
Loading...
Searching...
No Matches
TrafficSignHeightUtils.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//
4// This work is licensed under the terms of the MIT license.
5// For a copy, see <https://opensource.org/licenses/MIT>.
6
8
9#include "Carla.h"
10#include "TrafficSignBase.h"
11#include "Carla/Game/Tagger.h"
12
13#include <util/ue-header-guard-begin.h>
14#include "Components/BoxComponent.h"
15#include "Components/SceneComponent.h"
16#include "Engine/World.h"
17#include "GameFramework/Actor.h"
18#include "LandscapeProxy.h"
19#include <util/ue-header-guard-end.h>
20
21namespace crp = carla::rpc;
22
24{
25 namespace
26 {
27 /// Whether a hit surface is ground that a sign may stand on.
28 ///
29 /// The downward trace crosses every static object under the sign, so a
30 /// fence or any other prop beside the pole can answer before the ground
31 /// does. Taking the closest hit therefore lands the sign on top of that
32 /// prop, so each candidate is classified by its semantic tag instead.
33 bool IsGroundHit(const FHitResult &HitResult)
34 {
35 // Landscapes are not tagged by ATagger, so they carry no tag at all.
36 // On World Partition maps the collision belongs to the streaming
37 // proxies, so match the proxy base class rather than ALandscape.
38 const AActor *HitActor = HitResult.GetActor();
39 if (HitActor != nullptr && HitActor->IsA<ALandscapeProxy>())
40 {
41 return true;
42 }
43
44 const UPrimitiveComponent *Component = HitResult.GetComponent();
45 if (Component == nullptr)
46 {
47 return false;
48 }
49
50 switch (ATagger::GetTagOfTaggedComponent(*Component))
51 {
52 case crp::CityObjectLabel::Roads:
53 case crp::CityObjectLabel::RoadLines:
54 case crp::CityObjectLabel::Sidewalks:
55 case crp::CityObjectLabel::Ground:
56 case crp::CityObjectLabel::Terrain:
57 case crp::CityObjectLabel::Bridge:
58 case crp::CityObjectLabel::RailTrack:
59 return true;
60 default:
61 return false;
62 }
63 }
64 }
65
67 UWorld* World,
68 FVector& Location,
69 const TArray<AActor*>& IgnoredActors,
70 const TArray<UPrimitiveComponent*>& IgnoredComponents)
71 {
72 if (!World)
73 {
74 return false;
75 }
76
77 const FVector Start = Location + FVector(0.0f, 0.0f, 200.0f);
78 const FVector End = Location - FVector(0.0f, 0.0f, 10000.0f);
79
80 FCollisionQueryParams CollisionParams;
81 CollisionParams.bTraceComplex = true;
82 CollisionParams.bReturnPhysicalMaterial = false;
83 CollisionParams.AddIgnoredActors(IgnoredActors);
84 CollisionParams.AddIgnoredComponents(IgnoredComponents);
85
86 // Collect every surface under the sign rather than only the closest one,
87 // then keep the first that is ground. The hits come back ordered along the
88 // ray, so this is the highest ground below the sign.
89 //
90 // The trace must use the overlap channel, the same one URayTracer::CastRay
91 // uses. ECC_WorldStatic blocks, so a multi trace on it still stops at the
92 // first solid surface and a prop beside the pole hides the ground behind
93 // a single hit that the filter below then rejects, leaving no candidate.
94 TArray<FHitResult> HitResults;
95 World->LineTraceMultiByChannel(
96 HitResults, Start, End, ECC_GameTraceChannel3, CollisionParams);
97
98 constexpr float ZOffsetSignToGround = 0.5f;
99 for (const FHitResult &HitResult : HitResults)
100 {
101 if (IsGroundHit(HitResult))
102 {
103 Location.Z = HitResult.Location.Z + ZOffsetSignToGround;
104 return true;
105 }
106 }
107 return false;
108 }
109
111 UWorld* World,
112 ATrafficSignBase* Sign,
113 const TArray<AActor*>& IgnoredActors,
114 const TArray<UPrimitiveComponent*>& IgnoredComponents)
115 {
116 // bGeneratedFromOpenDRIVE is the guarantee that only signals spawned from
117 // the OpenDRIVE map are moved. ATrafficLightManager::TrafficSigns also
118 // holds hand-placed level actors that were matched to a signal, so the
119 // check belongs here, at the single choke point every caller goes through.
120 if (!World || !IsValid(Sign) || Sign->bPositioned ||
122 {
123 return false;
124 }
125
126 const FVector OriginalLocation = Sign->GetActorLocation();
127 FVector AdjustedLocation = OriginalLocation;
128
130 World, AdjustedLocation, IgnoredActors, IgnoredComponents))
131 {
132 UE_LOG(LogCarla, Verbose,
133 TEXT("Could not find ground for traffic sign %s at %s"),
134 *Sign->GetName(), *OriginalLocation.ToString());
135 return false;
136 }
137
138 const float ZOffset = AdjustedLocation.Z - OriginalLocation.Z;
139
140 USceneComponent* Root = Sign->GetRootComponent();
141 const EComponentMobility::Type OriginalMobility =
142 Root ? Root->Mobility.GetValue() : EComponentMobility::Static;
143 if (Root)
144 {
145 Root->SetMobility(EComponentMobility::Movable);
146 }
147
148 Sign->SetActorLocation(AdjustedLocation);
149
150 // Shift only the trigger volumes (not every box component on the actor) so
151 // they keep their world position while the visible mesh is grounded.
152 for (UBoxComponent* BoxComp : Sign->GetTriggerVolumes())
153 {
154 if (!BoxComp)
155 {
156 continue;
157 }
158 FVector BoxLocation = BoxComp->GetRelativeLocation();
159 BoxLocation.Z -= ZOffset;
160 BoxComp->SetRelativeLocation(BoxLocation);
161 }
162
163 Sign->UpdateComponentTransforms();
164 if (Root)
165 {
166 Root->SetMobility(OriginalMobility);
167 }
168
169 Sign->bPositioned = true;
170 UE_LOG(LogCarla, Log, TEXT("Adjusted sign %s height by %f cm"),
171 *Sign->GetName(), ZOffset);
172 return true;
173 }
174}
static bool IsValid(const ACarlaWheeledVehicle *Vehicle)
static crp::CityObjectLabel GetTagOfTaggedComponent(const UPrimitiveComponent &Component)
Retrieve the tag of an already tagged component.
Definition Tagger.cpp:213
bool bGeneratedFromOpenDRIVE
True for signs/lights generated from the OpenDRIVE map.
bool bPositioned
True once this sign has been snapped to the ground, to avoid adjusting it more than once.
TArray< UBoxComponent * > GetTriggerVolumes() const
Helpers to snap OpenDRIVE-spawned traffic signs / lights onto the ground so they are not left floatin...
bool AdjustSignToGround(UWorld *World, ATrafficSignBase *Sign, const TArray< AActor * > &IgnoredActors, const TArray< UPrimitiveComponent * > &IgnoredComponents)
Snap a single traffic sign to the ground and shift its trigger UBoxComponents by the same delta so th...
bool AdjustLocationToGround(UWorld *World, FVector &Location, const TArray< AActor * > &IgnoredActors, const TArray< UPrimitiveComponent * > &IgnoredComponents)
Line-trace downward to find the ground under Location.