CARLA
Loading...
Searching...
No Matches
RayCastSemanticLidar.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#include "Carla.h"
10#include "Carla/Game/Tagger.h"
12
14#include "carla/geom/Math.h"
15#include "carla/ros2/ROS2.h"
17
18#include <util/ue-header-guard-begin.h>
19#include "DrawDebugHelpers.h"
20#include "Engine/CollisionProfile.h"
21#include "Kismet/KismetMathLibrary.h"
22#include "PhysicsEngine/PhysicsObjectExternalInterface.h"
23#include "Async/ParallelFor.h"
24#include <util/ue-header-guard-end.h>
25#include "Landscape.h"
26
27#include <cmath>
28
29namespace crp = carla::rpc;
30
35
36ARayCastSemanticLidar::ARayCastSemanticLidar(const FObjectInitializer& ObjectInitializer)
37 : Super(ObjectInitializer)
38{
39 PrimaryActorTick.bCanEverTick = true;
40}
41
43{
44 Super::BeginPlay();
45 if (UCarlaLidarSubsystem* LidarSubsystem = GetWorld()->GetSubsystem<UCarlaLidarSubsystem>())
46 {
47 LidarSubsystem->RegisterLidar(this);
48 }
49}
50
51void ARayCastSemanticLidar::EndPlay(EEndPlayReason::Type EndPlayReason)
52{
53 if (UCarlaLidarSubsystem* LidarSubsystem = GetWorld()->GetSubsystem<UCarlaLidarSubsystem>())
54 {
55 LidarSubsystem->UnregisterLidar(this);
56 }
57 Super::EndPlay(EndPlayReason);
58}
59
60void ARayCastSemanticLidar::Set(const FActorDescription &ActorDescription)
61{
62 Super::Set(ActorDescription);
63 FLidarDescription LidarDescription;
64 UActorBlueprintFunctionLibrary::SetLidar(ActorDescription, LidarDescription);
65 Set(LidarDescription);
66}
67
75
77{
78 const auto NumberOfLasers = Description.Channels;
79 check(NumberOfLasers > 0u);
80 const float DeltaAngle = NumberOfLasers == 1u ? 0.f :
82 static_cast<float>(NumberOfLasers - 1);
83 LaserAngles.Empty(NumberOfLasers);
84 for(auto i = 0u; i < NumberOfLasers; ++i)
85 {
86 const float VerticalAngle =
87 Description.UpperFovLimit - static_cast<float>(i) * DeltaAngle;
88 LaserAngles.Emplace(VerticalAngle);
89 }
90}
91
92void ARayCastSemanticLidar::SendData(const float DeltaTime)
93{
94 TRACE_CPUPROFILER_EVENT_SCOPE(ARayCastSemanticLidar::SendData);
95 auto DataStream = GetDataStream(*this);
96 auto SensorTransform = DataStream.GetSensorTransform();
97 {
98 TRACE_CPUPROFILER_EVENT_SCOPE_STR("Send Stream");
99 DataStream.SerializeAndSend(*this, SemanticLidarData, DataStream.PopBufferFromPool());
100 }
101 // ROS2
102 #if defined(WITH_ROS2)
103 auto ROS2 = carla::ros2::ROS2::GetInstance();
104 if (ROS2->IsEnabled())
105 {
106 TRACE_CPUPROFILER_EVENT_SCOPE_STR("ROS2 Send");
108 AActor* ParentActor = GetAttachParentActor();
109 if (ParentActor)
110 {
111 FTransform LocalTransformRelativeToParent = GetActorTransform().GetRelativeTransform(ParentActor->GetActorTransform());
112 ROS2->ProcessDataFromSemanticLidar(DataStream.GetSensorType(), StreamId, LocalTransformRelativeToParent, SemanticLidarData, this);
113 }
114 else
115 {
116 ROS2->ProcessDataFromSemanticLidar(DataStream.GetSensorType(), StreamId, SensorTransform, SemanticLidarData, this);
117 }
118 }
119 #endif
120}
121
122void ARayCastSemanticLidar::SimulateLidar(const float DeltaTime, bool bLockPhysics)
123{
124 TRACE_CPUPROFILER_EVENT_SCOPE(ARayCastSemanticLidar::SimulateLidar);
125 const uint32 ChannelCount = Description.Channels;
126 const uint32 PointsToScanWithOneLaser =
127 FMath::RoundHalfFromZero(
128 Description.PointsPerSecond * DeltaTime / float(ChannelCount));
129
130 if (PointsToScanWithOneLaser <= 0)
131 {
132 UE_LOG(
133 LogCarla,
134 Warning,
135 TEXT("%s: no points requested this frame, try increasing the number of points per second."),
136 *GetName());
137 return;
138 }
139
140 check(ChannelCount == LaserAngles.Num());
141
142 const float CurrentHorizontalAngle = carla::geom::Math::ToDegrees(
144 const float AngleDistanceOfTick = Description.RotationFrequency * Description.HorizontalFov
145 * DeltaTime;
146 const float AngleDistanceOfLaserMeasure = AngleDistanceOfTick / PointsToScanWithOneLaser;
147 const float HalfHorizontalFov = Description.HorizontalFov / 2;
148
149 ResetDetections(ChannelCount, PointsToScanWithOneLaser);
150 PreprocessRays(ChannelCount, PointsToScanWithOneLaser);
151
152 const FTransform ActorTransform = GetTransform();
153 const FTransform InverseSensorTransform = ActorTransform.Inverse();
154 const FVector LidarBodyLocation = ActorTransform.GetLocation();
155 const FRotator LidarBodyRotation = ActorTransform.Rotator();
156
157 auto RunChannelRaycasts = [&]()
158 {
159 TRACE_CPUPROFILER_EVENT_SCOPE(ParallelFor);
160 ParallelFor(ChannelCount, [&](int32 idxChannel) {
161 TRACE_CPUPROFILER_EVENT_SCOPE(ParallelForTask);
162
163 FCollisionQueryParams TraceParams = FCollisionQueryParams(FName(TEXT("Laser_Trace")), true, this);
164 TraceParams.bTraceComplex = true;
165 TraceParams.bReturnPhysicalMaterial = false;
166
167 const float VertAngle = LaserAngles[idxChannel];
168 const std::vector<bool>& ChannelPreprocessCondition = RayPreprocessCondition[idxChannel];
169
170 for (auto idxPtsOneLaser = 0u; idxPtsOneLaser < PointsToScanWithOneLaser; idxPtsOneLaser++) {
171 // Rays dropped by a subclass's PreprocessRays.
172 if (!ChannelPreprocessCondition[idxPtsOneLaser])
173 {
174 continue;
175 }
176
177 FHitResult HitResult;
178 const float HorizAngle = std::fmod(CurrentHorizontalAngle + AngleDistanceOfLaserMeasure
179 * idxPtsOneLaser, Description.HorizontalFov) - HalfHorizontalFov;
180
181 if (ShootLaser(VertAngle, HorizAngle, HitResult, TraceParams, LidarBodyLocation, LidarBodyRotation)) {
182 WriteDetectionAsync(idxChannel, HitResult, InverseSensorTransform, LidarBodyLocation);
183 }
184 };
185 });
186 };
187
188 if (bLockPhysics)
189 {
190 auto LockedPhysObject = FPhysicsObjectExternalInterface::LockRead(GetWorld()->GetPhysicsScene());
191 RunChannelRaycasts();
192 LockedPhysObject.Release();
193 }
194 else
195 {
196 RunChannelRaycasts();
197 }
198
199 ComputeAndSaveDetections(ActorTransform);
200
201 const float HorizontalAngle = carla::geom::Math::ToRadians(
202 std::fmod(CurrentHorizontalAngle + AngleDistanceOfTick, Description.HorizontalFov));
203 SemanticLidarData.SetHorizontalAngle(HorizontalAngle);
204}
205
206void ARayCastSemanticLidar::PreprocessRays(uint32_t Channels, uint32_t MaxPointsPerChannel) {
207 RayPreprocessCondition.resize(Channels);
208
209 for (auto& conds : RayPreprocessCondition) {
210 conds.clear();
211 conds.resize(MaxPointsPerChannel);
212 std::fill(conds.begin(), conds.end(), true);
213 }
214}
215
216void ARayCastSemanticLidar::ResetDetections(uint32_t Channels, uint32_t MaxPointsPerChannel) {
217 ResetChannelDetections(SemanticDetections, Channels, MaxPointsPerChannel);
218}
219
220void ARayCastSemanticLidar::WriteDetectionAsync(uint32_t Channel, const FHitResult& HitInfo, const FTransform& InverseSensorTransform, const FVector& SensorLocation) {
221 DEBUG_ASSERT(GetChannelCount() > Channel);
222 FSemanticDetection& Detection = SemanticDetections[Channel].emplace_back();
223 ComputeRawDetection(HitInfo, InverseSensorTransform, SensorLocation, Detection);
224}
225
226void ARayCastSemanticLidar::ComputeAndSaveDetections(const FTransform& SensorTransform)
227{
228 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
229
230 for (auto idxChannel = 0u; idxChannel < Description.Channels; ++idxChannel)
231 {
232 PointsPerChannel[idxChannel] = SemanticDetections[idxChannel].size();
233 }
234
236 for (auto idxChannel = 0u; idxChannel < Description.Channels; ++idxChannel)
237 {
239 }
241}
242
243void ARayCastSemanticLidar::ComputeRawDetection(const FHitResult& HitInfo, const FTransform& InverseSensorTransform, const FVector& SensorLocation, FSemanticDetection& Detection) const
244{
245 static const uint32_t TerrainTag = static_cast<uint32_t>(ATagger::GetTagFromString("Terrain"));
246
247 const FVector HitPoint = HitInfo.ImpactPoint;
248 Detection.point = InverseSensorTransform.TransformPosition(HitPoint);
249
250 const FVector VecInc = - (HitPoint - SensorLocation).GetSafeNormal();
251 Detection.cos_inc_angle = FVector::DotProduct(VecInc, HitInfo.ImpactNormal);
252
253 const FActorRegistry &Registry = GetEpisode().GetActorRegistry();
254
255 const AActor* actor = HitInfo.GetActor();
256 Detection.object_idx = 0;
257 Detection.object_tag = static_cast<uint32_t>(crp::CityObjectLabel::None);
258
259 // Given that landscapes do not have tags for now, assign it here if the
260 // actor is a landscape, otherwise get the component tag.
261 // GetTagOfTaggedComponent tolerates untagged components (World Partition
262 // streams in HLOD/proxy geometry that never went through ATagger) by
263 // falling back to the stencil value, where reading ComponentTags[0]
264 // directly asserts on an empty array.
265 if (actor != nullptr && actor->IsA<ALandscape>())
266 {
267 Detection.object_tag = TerrainTag;
268 }
269 else if (HitInfo.Component.IsValid())
270 {
271 Detection.object_tag = static_cast<uint32_t>(ATagger::GetTagOfTaggedComponent(*HitInfo.Component));
272 }
273
274 if (actor != nullptr)
275 {
276 const FCarlaActor* view = Registry.FindCarlaActor(actor);
277 if (view != nullptr)
278 {
279 // On a Multi-GPU secondary, the id the primary's clients know.
280 Detection.object_idx = view->GetPrimaryActorId();
281 }
282 }
283 else
284 {
285 UE_LOG(LogCarla, Warning, TEXT("Actor not valid %p!!!!"), actor);
286 }
287}
288
289
290bool ARayCastSemanticLidar::ShootLaser(const float VerticalAngle, const float HorizontalAngle, FHitResult& HitResult, FCollisionQueryParams& TraceParams,
291 const FVector& LidarBodyLocation, const FRotator& LidarBodyRotation) const
292{
293 TRACE_CPUPROFILER_EVENT_SCOPE_STR(__FUNCTION__);
294
295 FRotator LaserRotation(VerticalAngle, HorizontalAngle, 0); // float InPitch, float InYaw, float InRoll
296 FRotator ResultRotation = UKismetMathLibrary::ComposeRotators(
297 LaserRotation,
298 LidarBodyRotation
299 );
300
301 const auto Range = Description.Range;
302 FVector EndTrace = Range * UKismetMathLibrary::GetForwardVector(ResultRotation) + LidarBodyLocation;
303
304 return GetWorld()->ParallelLineTraceSingleByChannel(
305 HitResult,
306 LidarBodyLocation,
307 EndTrace,
308 ECC_GameTraceChannel2,
309 TraceParams,
310 FCollisionResponseParams::DefaultResponseParam
311 );
312}
313
315{
316 State.lidar_horizontal_angle = SemanticLidarData.GetHorizontalAngle();
317 if (RandomEngine != nullptr)
318 {
319 State.random_engine = RandomEngine->GetState();
320 }
321}
322
324{
325 if (State.lidar_horizontal_angle)
326 {
327 SemanticLidarData.SetHorizontalAngle(*State.lidar_horizontal_angle);
328 }
329 if (State.random_engine && (RandomEngine != nullptr))
330 {
331 RandomEngine->SetState(*State.random_engine);
332 }
333}
#define DEBUG_ASSERT(predicate)
Definition Debug.h:66
carla::sensor::data::SemanticLidarData FSemanticLidarData
void GetMirrorState(carla::multigpu::SensorState &State) const override
Multi-GPU: the runtime state this sensor starts its next tick from (noise generator,...
static FActorDefinition GetSensorDefinition()
std::vector< uint32_t > PointsPerChannel
void SetMirrorState(const carla::multigpu::SensorState &State) override
virtual void SendData(const float DeltaTime)
virtual void BeginPlay() override
virtual void ComputeAndSaveDetections(const FTransform &SensorTransform)
Copies the per-channel detections into the LidarData structure.
virtual void PreprocessRays(uint32_t Channels, uint32_t MaxPointsPerChannel)
Method that allow to preprocess if the rays will be traced.
virtual void SimulateLidar(const float DeltaTime, bool bLockPhysics=true)
void CreateLasers()
Creates a Laser for each channel.
void ComputeRawDetection(const FHitResult &HitInfo, const FTransform &InverseSensorTransform, const FVector &SensorLocation, FSemanticDetection &Detection) const
Compute all raw detection information.
static void ResetChannelDetections(std::vector< std::vector< DetectionT > > &Detections, uint32_t Channels, uint32_t MaxPointsPerChannel)
Clears every channel buffer and reserves room for a full scan.
virtual void WriteDetectionAsync(uint32_t Channel, const FHitResult &HitInfo, const FTransform &InverseSensorTransform, const FVector &SensorLocation)
Computes the detection of a hit inside the raycast ParallelFor.
virtual void EndPlay(EEndPlayReason::Type EndPlayReason) override
virtual void Set(const FActorDescription &Description) override
ARayCastSemanticLidar(const FObjectInitializer &ObjectInitializer)
std::vector< std::vector< bool > > RayPreprocessCondition
virtual void ResetDetections(uint32_t Channels, uint32_t MaxPointsPerChannel)
Clear the per-channel detection buffers.
FLidarDescription Description
std::vector< std::vector< FSemanticDetection > > SemanticDetections
bool ShootLaser(const float VerticalAngle, float HorizontalAngle, FHitResult &HitResult, FCollisionQueryParams &TraceParams, const FVector &LidarBodyLocation, const FRotator &LidarBodyRotation) const
Shoot a laser ray-trace, return whether the laser hit something.
FSemanticLidarData SemanticLidarData
auto GetToken() const
Return the token that allows subscribing to this sensor's stream.
FAsyncDataStream GetDataStream(SensorType &&Self)
Return the FDataStream associated with this sensor.
URandomEngine * RandomEngine
Random Engine used to provide noise for sensor output.
static crp::CityObjectLabel GetTagOfTaggedComponent(const UPrimitiveComponent &Component)
Retrieve the tag of an already tagged component.
Definition Tagger.cpp:213
static crp::CityObjectLabel GetTagFromString(FString Tag)
Transform a string into a CityObjectLabel.
Definition Tagger.cpp:235
A registry of all the Carla actors.
FCarlaActor * FindCarlaActor(IdType Id)
A view over an actor and its properties.
Definition CarlaActor.h:25
IdType GetPrimaryActorId() const
The id clients know this actor by: the primary's for a mirror on a Multi-GPU secondary,...
Definition CarlaActor.h:150
static void SetLidar(const FActorDescription &Description, FLidarDescription &Lidar)
static FActorDefinition MakeLidarDefinition(const FString &Id)
const FActorRegistry & GetActorRegistry() const
Subsystem responsible for simulating all ray-cast LiDARs of a world.
uint32 GetState() const
The engine's state, to continue its exact sequence elsewhere (a Multi-GPU secondary's mirror of a sen...
void SetState(uint32 State)
Continue from State, as GetState returned it; an impossible value is ignored.
static constexpr T ToRadians(T deg)
Definition Math.h:43
static constexpr T ToDegrees(T rad)
Definition Math.h:37
static std::shared_ptr< ROS2 > GetInstance()
Definition ROS2.h:61
virtual void WriteChannelCount(std::vector< uint32_t > points_per_channel)
void WritePointsSync(const std::vector< SemanticLidarDetection > &detections)
virtual void ResetMemory(std::vector< uint32_t > points_per_channel)
Helper class to store and serialize the data generated by a RawLidar.
Serializes a stream endpoint.
A definition of a Carla Actor with all the variation and attributes.
A description of a Carla Actor with all its variation.
float HorizontalFov
Horizontal field of view.
float LowerFovLimit
Lower laser angle, counts from horizontal, negative values means under horizontal line.
float Range
Measure distance in centimeters.
uint32 Channels
Number of lasers.
uint32 PointsPerSecond
Points generated by all lasers per second.
float RotationFrequency
Lidar rotation frequency.
float UpperFovLimit
Upper laser angle, counts from horizontal, positive values means above horizontal line.
One record of frame data packet 27: the runtime state a sensor of the primary starts this frame's tic...
Definition sensorSync.h:87