Agrarsense
LidarManager.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
6#include "LidarManager.h"
7
13
14#include "ROSIntegration/Classes/ROSIntegrationGameInstance.h"
15#include "ROSIntegration/Classes/RI/Topic.h"
16
17#include "PhysicsEngine/PhysicsObjectExternalInterface.h"
18#include "Physics/Experimental/PhysScene_Chaos.h"
19#include "Physics/PhysicsInterfaceUtils.h"
20
21#include "Math/Vector.h"
22#include "Engine/CollisionProfile.h"
23#include "Runtime/Engine/Classes/Kismet/KismetMathLibrary.h"
24#include "Runtime/Core/Public/Async/ParallelFor.h"
25#include "Kismet/GameplayStatics.h"
26#include "Kismet/KismetMathLibrary.h"
27#include "Misc/CommandLine.h"
28
29#include <iterator>
30
31ALidarManager::ALidarManager(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
32{
33 // TickManager handles this Actor ticking
34 PrimaryActorTick.bCanEverTick = false;
35
36 if (FParse::Param(FCommandLine::Get(), TEXT("-save-combined-pointcloud")))
37 {
39 }
40}
41
43{
44 Super::BeginPlay();
45
46 // There should only be one instance of this actor which is spawned in AgrarsenseGameModebase.cpp
47 UWorld* World = GetWorld();
48 TArray<AActor*> FoundActors;
49 UGameplayStatics::GetAllActorsOfClass(World, ALidarManager::StaticClass(), FoundActors);
50 if (FoundActors.Num() != 1)
51 {
52#ifdef WITH_EDITOR
53 UE_LOG(LogTemp, Warning, TEXT("LidarManager.cpp: LidarManager already exists in the level. Destroying this one."));
54#endif
55 this->Destroy();
56 return;
57 }
58
59 RosInstance = Cast<UROSIntegrationGameInstance>(GetGameInstance());
60
62
63 FString DataLocation = UAgrarsensePaths::GetDataFolder();
64 FString UnixTimeStamp = FString::FromInt(FDateTime::Now().ToUnixTimestamp());
65 FileSavePath = DataLocation + "LidarCombined_" + UnixTimeStamp + "/";
66
67 // Initialize the PointCloudMessage
68 PointCloudMessage = MakeShared<ROSMessages::sensor_msgs::PointCloud2>();
69 PointCloudMessage->header.frame_id = "world";
70 PointCloudMessage->is_dense = true;
71 PointCloudMessage->fields.SetNum(4);
72 PointCloudMessage->height = 1;
73
74 PointCloudMessage->fields[0].name = "x";
75 PointCloudMessage->fields[0].offset = 0;
76 PointCloudMessage->fields[0].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
77 PointCloudMessage->fields[0].count = 1;
78
79 PointCloudMessage->fields[1].name = "y";
80 PointCloudMessage->fields[1].offset = 4;
81 PointCloudMessage->fields[1].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
82 PointCloudMessage->fields[1].count = 1;
83
84 PointCloudMessage->fields[2].name = "z";
85 PointCloudMessage->fields[2].offset = 8;
86 PointCloudMessage->fields[2].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
87 PointCloudMessage->fields[2].count = 1;
88
89 PointCloudMessage->fields[3].name = "rgb";
90 PointCloudMessage->fields[3].offset = 12;
91 PointCloudMessage->fields[3].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::UINT32;
92 PointCloudMessage->fields[3].count = 1;
93
94 PointCloudMessage->point_step = 16;
95}
96
97void ALidarManager::EndPlay(const EEndPlayReason::Type EndPlayReason)
98{
99 Super::EndPlay(EndPlayReason);
100
101 DestroyTopic();
102
103 PointCloudMessage.Reset();
104
105 RaycastLidars.clear();
106
107 RosInstance = nullptr;
108
109 delete(TotalAverageTimer);
110
111 if (TickAdded)
112 {
114 }
115}
116
117void ALidarManager::TickParallel(float DeltaTime)
118{
119#ifdef WITH_EDITOR
121 {
123 }
124#endif
125
126 SimulateRaycastLidars(DeltaTime);
127
128#ifdef WITH_EDITOR
130 {
131 double ms = TotalAverageTimer->PrintAverageTime();
132 TimeInMilliseconds = FMath::RoundToFloat(ms * 100.0f) / 100.0f;
133 }
134#endif
135}
136
137void ALidarManager::SimulateRaycastLidars(const float DeltaTime)
138{
139 TRACE_CPUPROFILER_EVENT_SCOPE(ALidarManager::SimulateRaycastLidars);
140
141 const int lidarCount = RaycastLidars.size();
142
143 if (lidarCount == 0 || !SimulateLidars)
144 {
145 return;
146 }
147
148 std::vector<FPointData> flattenedPoints;
149
150 if (lidarCount == 1)
151 {
152 // If we only have one lidar sensor,
153 // we can skip ParallelFor and 2D to 1D std::vector conversion overhead completely
154 ALidar* lidar = RaycastLidars[0];
155 if (lidar)
156 {
157#ifdef ParallelLineTraceSingleByChannel_EXISTS
158 // ParallelLineTraceSingleByChannel_EXISTS Defined and implemented in AGRARSENSE Unreal Engine fork
159 // Lock physics scene while linetracing.
160 auto PhysicsLock = FPhysicsObjectExternalInterface::LockRead(GetWorld()->GetPhysicsScene());
161 {
162 flattenedPoints = lidar->SimulateRaycastLidar(DeltaTime);
163 }
164 PhysicsLock.Release();
165#else
166 // If not using AGRARSENSE fork no need to lock the physics scene.
167 flattenedPoints = lidar->SimulateRaycastLidar(DeltaTime);
168#endif
169 }
170 }
171 else
172 {
173 // Else we have multiple Lidar sensors,
174 // Simulate all of them in parallel
175 std::vector<std::vector<FPointData>> points(lidarCount);
176 ParallelFor(lidarCount, [&](int32 lidarIndex)
177 {
178 ALidar* lidar = RaycastLidars[lidarIndex];
179 if (lidar)
180 {
181#ifdef ParallelLineTraceSingleByChannel_EXISTS
182 // ParallelLineTraceSingleByChannel_EXISTS Defined and implemented in AGRARSENSE Unreal Engine fork
183 // Lock physics scene while linetracing.
184 auto PhysicsLock = FPhysicsObjectExternalInterface::LockRead(GetWorld()->GetPhysicsScene());
185 {
186 points[lidarIndex] = lidar->SimulateRaycastLidar(DeltaTime);
187 }
188 PhysicsLock.Release();
189#else
190 // If not using AGRARSENSE fork no need to lock the physics scene.
191 points[lidarIndex] = lidar->SimulateRaycastLidar(DeltaTime);
192#endif
193 }
194 }, EParallelForFlags::Unbalanced);
195
197 {
198 int32 TotalSize = 0;
199
200 for (const auto& subVector : points)
201 {
202 TotalSize += subVector.size();
203 }
204
205 if (TotalSize != 0)
206 {
207 flattenedPoints.reserve(TotalSize);
208 for (auto& subVector : points)
209 {
210 flattenedPoints.insert(flattenedPoints.end(), std::make_move_iterator(subVector.begin()), std::make_move_iterator(subVector.end()));
211 }
212 }
213 }
214 }
215
216 if (!flattenedPoints.empty())
217 {
219 {
220 SaveCombinedPointcloud(flattenedPoints);
221 }
222
223 SendCombinedPointcloud(flattenedPoints);
224 }
225}
226
227void ALidarManager::SetSimulateLidars(bool bSimulateLidars)
228{
229 SimulateLidars = bSimulateLidars;
230}
231
232void ALidarManager::SetMeasurePerformance(bool bMeasurePerformance)
233{
234 MeasurePerformance = bMeasurePerformance;
235}
236
237void ALidarManager::SetSaveCombinedCloudToDisk(bool bSaveCombinedCloud)
238{
239 SaveCombinedCloudToDisk = bSaveCombinedCloud;
240}
241
242void ALidarManager::SetSendCombinedCloud(bool bSaveCombinedCloud)
243{
244 SendCombinedCloudToROS = bSaveCombinedCloud;
246 {
247 InitTopic();
248 }
249
250#if WITH_EDITOR
251 UE_LOG(LogTemp, Warning, TEXT("LidarManager.cpp: Saving combined Lidar pointcloud to disk: %s"), (SendCombinedCloudToROS ? TEXT("true") : TEXT("false")));
252#endif
253}
254
256{
257 if (!lidarPtr)
258 {
259 return;
260 }
261
262 RaycastLidars.push_back(lidarPtr);
263
264#if WITH_EDITOR
265 UE_LOG(LogTemp, Warning, TEXT("LidarManager.cpp: Added Lidar sensor."));
266#endif
267
268 if (!TickAdded)
269 {
270 InitTopic();
271
272 TickEntry = ATickManager::AddTick(this, BindTick(this, &ALidarManager::TickParallel), ETickType::FullFrameTaskParallel);
273 TickAdded = true;
274 }
275
276 // Broadcast new lidar has been spawned, broadcast Lidar actor
277 OnLidarSpawned.Broadcast(lidarPtr);
278}
279
281{
282 if (!lidarPtr)
283 {
284 return;
285 }
286
287 auto it = std::find(RaycastLidars.begin(), RaycastLidars.end(), lidarPtr);
288 if (it != RaycastLidars.end())
289 {
290 RaycastLidars.erase(it);
291
292#if WITH_EDITOR
293 UE_LOG(LogTemp, Warning, TEXT("LidarManager.cpp: Removed Lidar sensor"));
294#endif
295 }
296
297 // Disable tick if there's no Lidar sensors.
298 if (RaycastLidars.size() == 0)
299 {
301 if (TickAdded)
302 {
304 TickAdded = false;
305 DestroyTopic();
306 }
307 }
308
309 // Broadcast lidar sensor has been destroyed event and broadcast current lidar count
310 int currentLidarCount = RaycastLidars.size();
311 OnLidarDestroyed.Broadcast(currentLidarCount);
312}
313
315{
316 switch (RosState)
317 {
319 InitTopic();
320 break;
321
323 DestroyTopic();
324 break;
325 }
326}
327
329{
330 if (CombinedLidarTopic.IsValid() || !SendCombinedCloudToROS || !RosInstance->IsROSConnected())
331 {
332 return;
333 }
334
335 CombinedLidarTopic = NewObject<UTopic>(UTopic::StaticClass());
336 CombinedLidarTopic->Init(RosInstance->ROSIntegrationCore, FString::Printf(TEXT("/agrarsense/out/sensors/lidars_combined")), TEXT("sensor_msgs/PointCloud2"));
337 CombinedLidarTopic->Advertise();
338
339#if WITH_EDITOR
340 UE_LOG(LogTemp, Warning, TEXT("LidarManager.cpp: Created combined Lidar ROS topic. Topic name: /agrarsense/sensors/lidars_combined"));
341#endif
342}
343
345{
346 if (CombinedLidarTopic.IsValid())
347 {
348 CombinedLidarTopic->Unadvertise();
349 CombinedLidarTopic->Unsubscribe();
350 CombinedLidarTopic->MarkAsDisconnected();
351 CombinedLidarTopic->ConditionalBeginDestroy();
352 CombinedLidarTopic.Reset();
353 }
354}
355
356void ALidarManager::SaveCombinedPointcloud(const std::vector<FPointData>& points)
357{
359 {
360 return;
361 }
362
363 FString Filename = FString::Printf(TEXT("%sCombinedPointcloud_%d.ply"), *FileSavePath, LidarSaves);
364 ++LidarSaves;
365
367}
368
369void ALidarManager::SendCombinedPointcloud(const std::vector<FPointData>& points)
370{
371 TRACE_CPUPROFILER_EVENT_SCOPE(ALidarManager::SendCombinedPointcloud);
372
373 UTopic* Topic = CombinedLidarTopic.Get();
374
376 || !RosInstance
377 || !RosInstance->IsROSConnected()
378 || !Topic
379 || !PointCloudMessage.IsValid())
380 {
381 return;
382 }
383
384 const int pointSize = points.size();
385 size_t dataSize = pointSize * sizeof(FPointData);
386
387 PointCloudMessage->width = pointSize;
388 PointCloudMessage->row_step = dataSize;
389 PointCloudMessage->data_ptr = reinterpret_cast<uint8*>(const_cast<FPointData*>(points.data()));
390
391 Topic->Publish(PointCloudMessage);
392}
EROSState
Definition: ROSState.h:16
static auto BindTick(ObjectType *Object, FunctionType Function)
Definition: TickManager.h:182
void SendCombinedPointcloud(const std::vector< FPointData > &points)
FString FileSavePath
Definition: LidarManager.h:213
FLidarDelegate_LidarSpawned OnLidarSpawned
Definition: LidarManager.h:131
UROSIntegrationGameInstance * RosInstance
Definition: LidarManager.h:204
void SaveCombinedPointcloud(const std::vector< FPointData > &points)
TSharedPtr< ROSMessages::sensor_msgs::PointCloud2 > PointCloudMessage
Definition: LidarManager.h:217
virtual void BeginPlay() override
TWeakObjectPtr< UTopic > CombinedLidarTopic
Definition: LidarManager.h:207
void SetSaveCombinedCloudToDisk(bool bSaveCombinedCloud)
void AddRaycastLidar(ALidar *lidarPtr)
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
void SimulateRaycastLidars(const float DeltaTime)
bool SaveCombinedCloudToDisk
Definition: LidarManager.h:144
FTickEntry TickEntry
Definition: LidarManager.h:160
void ROSBridgeStateChanged(EROSState RosState)
void SetSendCombinedCloud(bool bSaveCombinedCloud)
void SetMeasurePerformance(bool bMeasurePerformance)
void SetSimulateLidars(bool bSimulateLidars)
float TimeInMilliseconds
Definition: LidarManager.h:150
void RemoveRaycastLidar(ALidar *lidarPtr)
SimpleTimer * TotalAverageTimer
Definition: LidarManager.h:209
ALidarManager(const FObjectInitializer &ObjectInitializer)
bool SendCombinedCloudToROS
Definition: LidarManager.h:141
std::vector< ALidar * > RaycastLidars
Definition: LidarManager.h:201
FLidarDelegate_LidarDestroyed OnLidarDestroyed
Definition: LidarManager.h:138
void TickParallel(float DeltaTime)
bool MeasurePerformance
Definition: LidarManager.h:147
Definition: Lidar.h:34
std::vector< FPointData > SimulateRaycastLidar(const float DeltaTime)
Definition: Lidar.cpp:273
static void RemoveTick(FTickEntry TickEntry)
Definition: TickManager.cpp:90
static FTickEntry AddTick(UObject *Object, std::function< void(float)> Function, ETickType Type)
Definition: TickManager.cpp:55
double PrintAverageTime(FString prefix="Average execution time ", FString suffix=" milliseconds", bool printToScreen=false)
Definition: SimpleTimer.h:123
void TakeTimeStamp()
Definition: SimpleTimer.h:76
void ResetAverageTime()
Definition: SimpleTimer.h:141
static FString GetDataFolder()
static void SaveVectorArrayAsPlyAsync(FString FullFileName, const std::vector< FPointData > points)