Agrarsense
Lidar.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 "Lidar.h"
7
8#include "LidarNoiseModel.h"
16
17#include "Runtime/Engine/Classes/Kismet/KismetMathLibrary.h"
18#include "NiagaraDataInterfaceArrayFunctionLibrary.h"
19#include "Math/UnrealMathUtility.h"
20#include "NiagaraFunctionLibrary.h"
21#include <Misc/FileHelper.h>
22#include "NiagaraComponent.h"
23#include "Components/PrimitiveComponent.h"
24#include "Landscape.h"
25#include "Async/ParallelFor.h"
26#include "Engine/GameViewportClient.h"
27#include "Engine/Engine.h"
28
29#include <iterator>
30
31ALidar::ALidar(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
32{
33 // Lidar sensors are ticked by LidarManager.cpp
34 PrimaryActorTick.bCanEverTick = false;
35}
36
37void ALidar::Init(FLidarParameters parameters, bool SimulateSensor)
38{
39 SetSimulateSensor(SimulateSensor);
40 LidarParameters = parameters;
43
44 // Initialize PointCloudMessage
45 PointCloudMessage.Reset();
46 PointCloudMessage = MakeShared<ROSMessages::sensor_msgs::PointCloud2>();
47 PointCloudMessage->header.frame_id = "world";
48 PointCloudMessage->is_dense = true;
49 PointCloudMessage->fields.SetNum(4);
50 PointCloudMessage->height = 1;
51
52 PointCloudMessage->fields[0].name = "x";
53 PointCloudMessage->fields[0].offset = 0;
54 PointCloudMessage->fields[0].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
55 PointCloudMessage->fields[0].count = 1;
56
57 PointCloudMessage->fields[1].name = "y";
58 PointCloudMessage->fields[1].offset = 4;
59 PointCloudMessage->fields[1].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
60 PointCloudMessage->fields[1].count = 1;
61
62 PointCloudMessage->fields[2].name = "z";
63 PointCloudMessage->fields[2].offset = 8;
64 PointCloudMessage->fields[2].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::FLOAT32;
65 PointCloudMessage->fields[2].count = 1;
66
67 PointCloudMessage->fields[3].name = "rgb";
68 PointCloudMessage->fields[3].offset = 12;
69 PointCloudMessage->fields[3].datatype = ROSMessages::sensor_msgs::PointCloud2::PointField::UINT32;
70 PointCloudMessage->fields[3].count = 1;
71
72 PointCloudMessage->point_step = 16;
73
74 Generator.seed(RandomDevice());
75}
76
78{
79 Super::BeginPlay();
80
81 World = GetWorld();
82
83 // Create the SemanticColors TMap
84 TMap<FString, FColor> Colors = GetSemanticColors();
85 for (const auto& ColorEntry : Colors)
86 {
87 FName SemanticName = FName(*ColorEntry.Key);
88 SemanticColors.Add(SemanticName, ColorEntry.Value);
89 }
90
91 // Cache "None" and "Snowflake" colors for Semantic
92 FColor NoneColor = SemanticColors.FindRef("None");
93 FColor SnowflakeColor = SemanticColors.FindRef("Snowflake");
94 CachedNoneColor = (NoneColor.R << 16) | (NoneColor.G << 8) | (NoneColor.B << 0);
95 CachedSnowflakeColor = (SnowflakeColor.R << 16) | (SnowflakeColor.G << 8) | (SnowflakeColor.B << 0);
96
97 // Add this lidar to LidarManager that handles ticking of this sensor in parallel.
99 if (LidarManager)
100 {
101 LidarManager->AddRaycastLidar(this);
102 }
103
105 if (Weather)
106 {
108 Weather->OnWeatherChanged.AddUniqueDynamic(this, &ALidar::OnWeatherChanged);
109 }
110
111 // Load pointcloud Niagara particle system
112 UNiagaraSystem* NS = LoadObject<UNiagaraSystem>(nullptr, TEXT("/Game/Agrarsense/Particles/PointCloud/NS_PointCloudSemanticFast"), nullptr, LOAD_None, nullptr);
113 if (NS)
114 {
115 NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(World, NS, GetActorLocation());
117 {
118 // Hide this NiagaraComponent for all Camera sensors.
119 HideComponentForAllCameras(Cast<UPrimitiveComponent>(NiagaraComponent));
120
121 NiagaraComponent->SetAffectDynamicIndirectLighting(false);
122 NiagaraComponent->SetAffectDistanceFieldLighting(false);
123 NiagaraComponent->SetCastContactShadow(false);
124 NiagaraComponent->SetCastShadow(false);
125 }
126 }
127#if WITH_EDITOR
128 else
129 {
130 UE_LOG(LogTemp, Warning, TEXT("Lidar.cpp: Couldn't find Niagara particle system."));
131 }
132#endif
133}
134
135void ALidar::EndPlay(const EEndPlayReason::Type EndPlayReason)
136{
137 Super::EndPlay(EndPlayReason);
138
139 World = GetWorld();
140
141 // Remove this lidar from LidarManager
143 if (LidarManager)
144 {
145 LidarManager->RemoveRaycastLidar(this);
146 }
147
148 // Unbind from Weather changed event
150 if (Weather)
151 {
152 Weather->OnWeatherChanged.RemoveDynamic(this, &ALidar::OnWeatherChanged);
153 }
154
156 {
157 SetNiagaraRendering(false);
158
159 // Destroy component
160 NiagaraComponent->UnregisterComponent();
161 NiagaraComponent->DestroyComponent();
162 NiagaraComponent = nullptr;
163 }
164
165 PointCloudMessage.Reset();
166
167 World = nullptr;
168
169 // Clear all containers on EndPlay
171 PointsPerChannel.clear();
172 PointsFlattened.clear();
173 LaserAngles.clear();
174 Points.clear();
175}
176
178{
179 CurrentWeatherParameters = WeatherParams;
180
181 // Here we recalculate SnowHeightOffset when Weather changes.
182 // Amount is fine-tuned by hand. If the Snow implementation would change meaningfully,
183 // we would need to change Amount value too.
184 // See AddProcessingToHitResult() function how SnowHeightOffset is used.
185 const float Amount = 0.1f;
187}
188
190{
191 // Ensure certain parameters are valid ranges
192 LidarParameters.HorizontalFov = FMath::Clamp(LidarParameters.HorizontalFov, 0.1f, 360.0f);
196
197 const int32 NumberOfLasers = LidarParameters.Channels;
198
199 // Resize arrays
200 PreProcessedHitImpactPoints.resize(NumberOfLasers);
201 Points.resize(NumberOfLasers);
202
203 // Create Lidar linetrace angles
204 const float DeltaAngle = NumberOfLasers == 1u ? 0.f : (LidarParameters.UpperFovLimit - LidarParameters.LowerFovLimit) / static_cast<float>(NumberOfLasers - 1);
205 LaserAngles.clear();
206
207 for (int32 i = 0u; i < NumberOfLasers; ++i)
208 {
209 const float VerticalAngle = LidarParameters.UpperFovLimit - static_cast<float>(i) * DeltaAngle;
210 LaserAngles.emplace_back(VerticalAngle);
211 }
212
216
217
218 if (LidarParameters.PointsPerSecond > 1000000)
219 {
220 SimulatorLog::Log("Lidar sensor: It's highly advisable to turn off VisualizePointcloud when PointsPerSecond exceed 1 million.");
221
222 if (LidarParameters.PointsPerSecond > 2000000)
223 {
225 SimulatorLog::Log("Lidar sensor: VisualizePointcloud has been set to false due PointsPerSecond exceeding 2 million.");
226 }
227 }
228
230
232}
233
235{
236 SetNiagaraRendering(Visualize);
237}
238
240{
242 {
243 if (!Enabled)
244 {
245 TArray<FVector> EmptyHitpoints;
246 TArray<FLinearColor> EmptyColors;
247 UpdateLidarParticles(0, EmptyHitpoints, EmptyColors);
248 }
249
250 NiagaraComponent->SetRenderingEnabled(Enabled);
251 }
252}
253
254void ALidar::UpdateLidarParticles(int NumberOfHits, const TArray<FVector>& HitPoints, const TArray<FLinearColor>& Colors)
255{
257 {
258 NiagaraComponent->SetVariableFloat(NiagaraPointsFloat, float(NumberOfHits));
259 NiagaraComponent->SetVariableInt(NiagaraPointsInt, NumberOfHits);
260 UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayVector(NiagaraComponent, NiagaraHitPoints, HitPoints);
261 UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayColor(NiagaraComponent, NiagaraHitColors, Colors);
262 }
263}
264
265void ALidar::SetParticleLifeTime(float ParticleLifeTime)
266{
268 {
269 NiagaraComponent->SetVariableFloat(FName("User.LifeTime"), ParticleLifeTime);
270 }
271}
272
273 std::vector<FPointData> ALidar::SimulateRaycastLidar(const float DeltaTime)
274{
275 TRACE_CPUPROFILER_EVENT_SCOPE(ALidar::SimulateRaycastLidar);
276
277 std::vector<FPointData> HitPoints;
278
279 if (!CanSimulateSensor())
280 {
281 return HitPoints;
282 }
283
284 // Get sensor position and rotation
285 const FTransform actorTransform = GetTransform();
286 const FVector LidarBodyLocation = actorTransform.GetLocation();
287 const FRotator LidarBodyRotation = actorTransform.Rotator();
288 const FQuat LidarBodyQuat = FQuat(LidarBodyRotation);
289
290 // Prepare needed variables for processing
291 const int32 ChannelCount = LidarParameters.Channels;
292 const int32 PointsToScanWithOneChannel = FMath::RoundHalfFromZero(LidarParameters.PointsPerSecond * DeltaTime / float(ChannelCount));
293 const float HorizontalAngle = CurrentHorizontalAngle;
294 const float AngleDistanceOfTick = LidarParameters.RotationFrequency * LidarParameters.HorizontalFov * DeltaTime;
295 const float AngleDistanceOfLaserMeasure = AngleDistanceOfTick / PointsToScanWithOneChannel;
296 const float Range = LidarParameters.Range * 100; // to meters
297
298 const FColor Color = FColor(255, 255, 255);
299 const int32 RgbDefault = (Color.R << 16) | (Color.G << 8) | (Color.B << 0);
300
301 FCollisionQueryParams TraceParams = FCollisionQueryParams(FName(TEXT("Laser_Trace")), true, this);
302 TraceParams.bReturnPhysicalMaterial = false;
303
304 // Note. can become very expensive with high vertice meshes.
305 // Any very high vertice mesh should have simpler collision mesh (Tree for example)
306 TraceParams.bTraceComplex = LidarParameters.UseComplexCollisionTrace;
307
308 // Check if we are going to add any noise
312
313 // Check if separate point cloud should be saved without the noise
314 NeedToSavePointCloudWithoutNoise = ShouldAddNoise
317
318 const bool Semantic = LidarParameters.Semantic;
319 const float HorizontalFOV = LidarParameters.HorizontalFov;
320 const float HorizontalFOVHalf = HorizontalFOV / 2;
321
322 ResetRecordedHits(PointsToScanWithOneChannel);
323
324 // Loop Lidar channels in Parallel
325 ParallelFor(ChannelCount, [&](int32 Channel)
326 {
327 const float VerticalAngle = LaserAngles[Channel];
328 int32 RGB = RgbDefault;
329
330 FRotator LaserRotation;
331 FHitResult HitResult;
332 FVector EndTrace;
333
334 // Generate random bool which we use later in AddLateralNoise
335 bool UseHorizontalNoise = FMath::RandBool();
336
337 // Cache the reference to Points[idxChannel]
338 std::vector<FPointData>& ChannelPoints = Points[Channel];
339
340 // Loop through this channel linetraces
341 for (int32 i = 0; i < PointsToScanWithOneChannel; i++)
342 {
343 const float LaserHorizontalAngle = std::fmod(HorizontalAngle + AngleDistanceOfLaserMeasure * i, HorizontalFOV) - HorizontalFOVHalf;
344
345 HitResult.Reset();
346
347 // Calculate the end trace point for this linetrace
348 LaserRotation = FRotator(VerticalAngle, LaserHorizontalAngle, 0);
349 FQuat LaserQuat = FQuat(LaserRotation);
350 FQuat ResultQuat = LaserQuat * LidarBodyQuat;
351 EndTrace = Range * ResultQuat.Vector() + LidarBodyLocation;
352
353 if (ShootLaser(HitResult, EndTrace, TraceParams, LidarBodyLocation, Channel, Semantic, RgbDefault))
354 {
355 if (Semantic)
356 {
357 RGB = GetSemanticData(HitResult);
358 }
359
360 AddProcessingToHitResult(HitResult, LidarBodyLocation, UseHorizontalNoise);
361
362 // Add HitResult impact point to the current channel's point list
363 const FVector& HitPoint = HitResult.ImpactPoint;
364 ChannelPoints.emplace_back(HitPoint.X, HitPoint.Y, HitPoint.Z, RGB);
365 }
366 }
367 }, EParallelForFlags::Unbalanced);
368
369 CurrentHorizontalAngle = FMath::Fmod(HorizontalAngle + AngleDistanceOfTick, LidarParameters.HorizontalFov);
370
371 SendData(DeltaTime);
372
374 {
378 }
379
381 {
382 return PointsFlattened;
383 }
384
385 return HitPoints;
386}
387
388void ALidar::AddProcessingToHitResult(FHitResult& HitResult, const FVector& LidarBodyLocation, const bool UseHorizontalNoise)
389{
390 AddSnowTerrainAdjustment(HitResult, LidarBodyLocation);
391 AddDistanceNoise(HitResult, LidarBodyLocation);
392 AddLateralNoise(HitResult, LidarBodyLocation, UseHorizontalNoise);
393}
394
395void ALidar::AddDistanceNoise(FHitResult& HitResult, const FVector& LidarBodyLocation)
396{
398 {
399 std::normal_distribution<float> Distribution(0.0f, LidarParameters.DistanceNoiseStdDev);
400 float NoiseValue = Distribution(Generator);
401 const FVector DirectionToSensor = ((LidarBodyLocation * 1e-2) - HitResult.ImpactPoint).GetSafeNormal();
402 const FVector Noise = DirectionToSensor * NoiseValue;
403 HitResult.ImpactPoint += Noise;
404 }
405}
406
407void ALidar::AddLateralNoise(FHitResult& HitResult, const FVector& LidarBodyLocation, const bool UseHorizontalNoise)
408{
410 {
411 const FVector ForwardVector = (HitResult.ImpactPoint - LidarBodyLocation).GetSafeNormal();
412 const FVector RightVector = FVector::CrossProduct(ForwardVector, FVector::UpVector).GetSafeNormal();
413
414 FVector ChosenDirection;
415 if (UseHorizontalNoise)
416 {
417 ChosenDirection = RightVector;
418 }
419 else
420 {
421 FVector UpVector = FVector::CrossProduct(ForwardVector, RightVector).GetSafeNormal();
422 ChosenDirection = UpVector;
423 }
424
425 std::normal_distribution<float> Distribution(0.0f, LidarParameters.LateralNoiseStdDev);
426 const float NoiseValue = Distribution(Generator);
427
428 HitResult.ImpactPoint += ChosenDirection * NoiseValue;
429 }
430}
431
432void ALidar::AddSnowTerrainAdjustment(FHitResult& HitResult, const FVector& LidarBodyLocation)
433{
435 {
436 bool HitTerrain = false;
437 AActor* HitActor = HitResult.GetActor();
438
439 // Terrain should have tag "Terrain" in order to make this work.
440 if (HitActor && HitActor->Tags.Contains("Terrain"))
441 {
442 HitTerrain = true;
443 }
444
445 if (HitTerrain)
446 {
447 // Here we adjust HitResult ImpactPoint if Linetrace hits "Terrain" and there is Snow.
448 // This is done because the simulator snow implementation
449 // is using World Position Offset in material, meaning it has no actual collider where linetrace could hit.
450 // Thus we need to manually calculate the rough point where Lidar linetrace goes through the snow layer.
451
452 // ImpactPoint is adjusted based on the SnowHeightOffset calculated during OnWeatherChanged function.
453 // SnowHeightOffset represents the height at which the Linetrace goes through the snow layer.
454 // We use linear interpolation (Lerp) to smoothly adjust the ImpactPoint towards LidarBodyLocation.
455 FVector NewHitPoint = FMath::Lerp(HitResult.ImpactPoint, (LidarBodyLocation * 1e-2), SnowHeightOffset);
456 HitResult.ImpactPoint = NewHitPoint;
457 }
458 }
459}
460
462{
463 TRACE_CPUPROFILER_EVENT_SCOPE(ALidar::VisualizeLidarParticles);
464
466 {
467 return;
468 }
469
470 if (GEngine
471 && GEngine->GameViewport
472 && GEngine->GameViewport->bDisableWorldRendering)
473 {
474 // If world rendering (Spectator camera) is disabled, no need to update the particle system.
475 return;
476 }
477
478 int32 NumberOfHits = PointsFlattened.size();
479 if (NumberOfHits != 0)
480 {
481 // Convert std::vector<FPointData> to
482 // TArray<FVector> and TArray<FLinearColor> for Niagara visualization.
483
484 // Create and allocate HitLocations TArray
485 TArray<FVector> HitLocations;
486 HitLocations.SetNumUninitialized(NumberOfHits);
487
488 // Create and set initial colors to green
489 TArray<FLinearColor> Colors;
490 Colors.Init(FLinearColor::Green, NumberOfHits);
491
492 // Pointer access for reduced overhead
493 FVector* HitLocationPtr = HitLocations.GetData();
494 FLinearColor* ColorsPtr = Colors.GetData();
495
496 const bool Semantic = LidarParameters.Semantic;
497
498 for (int32 i = 0; i < NumberOfHits; ++i)
499 {
500 const FPointData& point = PointsFlattened[i];
501
502 HitLocationPtr[i] = FVector(point.X, (point.Y * -1), point.Z);
503
504 if (Semantic)
505 {
506 // Convert point.RGB (uint32) to FLinearColor
507 uint8 R = (point.RGB >> 16) & 0xFF;
508 uint8 G = (point.RGB >> 8) & 0xFF;
509 uint8 B = point.RGB & 0xFF;
510 ColorsPtr[i] = FLinearColor(R / 255.0f, G / 255.0f, B / 255.0f);
511 }
512 }
513 UpdateLidarParticles(NumberOfHits, HitLocations, Colors);
514 }
515}
516
517void ALidar::SendData(const float DeltaTime)
518{
519 if (!CanSendData(DeltaTime))
520 {
521 return;
522 }
523
524 // Pre allocate size to PointsFlattened
525 size_t totalSize = 0;
526 for (const auto& vec : Points)
527 {
528 totalSize += vec.size();
529 }
530 PointsFlattened.reserve(totalSize);
531
532 // Fill PointsFlattened array
533 const int32 channelCount = LidarParameters.Channels;
534 for (int32 idxChannel = 0; idxChannel < channelCount; ++idxChannel)
535 {
536 PointsFlattened.insert(PointsFlattened.end(), std::make_move_iterator(Points[idxChannel].begin()), std::make_move_iterator(Points[idxChannel].end()));
537 }
538
540 {
542 }
543
544 if (SendDataToROS)
545 {
547 }
548}
549
550bool ALidar::CanSendData(const float DeltaTime)
551{
553 {
554 ClearContainers = true;
555 return true;
556 }
557
558 SensorHzTimer += DeltaTime;
560 {
561 SensorHzTimer = 0.0f;
562 ClearContainers = true;
563 return true;
564 }
565 else
566 {
567 ClearContainers = false;
568 return false;
569 }
570}
571
573{
574 if (!CanSimulateSensor())
575 {
576 // We are not currently simulating sensor, we can safely change parameters here
577 LidarParameters = newLidarParameters;
579 }
580 else
581 {
582 // Change parameters at the end of the frame
583 TempLidarParameters = newLidarParameters;
585 }
586}
587
588uint32 ALidar::GetSemanticData(const FHitResult& HitResult) const
589{
590 auto* HitComponent = HitResult.Component.Get();
591
592 if (HitComponent)
593 {
594 if (!HitComponent->ComponentTags.IsEmpty())
595 {
596 FName Tag = HitComponent->ComponentTags[0];
597 FColor Color = SemanticColors.FindRef(Tag);
598 return (Color.R << 16) | (Color.G << 8) | (Color.B << 0);
599 }
600 else
601 {
602 return CachedNoneColor;
603 }
604 }
605 else
606 {
608 }
609}
610
611bool ALidar::ShootLaser(FHitResult& HitResult, const FVector& EndTrace, const FCollisionQueryParams& TraceParams,
612 const FVector& LidarBodyLocation, const int32 Channel, const bool Semantic, const uint32 RGBDefault)
613{
614#ifdef ParallelLineTraceSingleByChannel_EXISTS
615 // Defined and implemented in our AGRARSENSE Unreal Engine fork
616 // This LineTrace method doesn't block the physics scene which improves linetrace performance.
617 World->ParallelLineTraceSingleByChannel(
618 HitResult,
619 LidarBodyLocation,
620 EndTrace,
621 ECC_GameTraceChannel3,
622 TraceParams,
623 FCollisionResponseParams::DefaultResponseParam);
624#else
625 // If not using our fork of the engine, use default LineTrace method.
626 World->LineTraceSingleByChannel(
627 HitResult,
628 LidarBodyLocation,
629 EndTrace,
630 ECC_GameTraceChannel3,
631 TraceParams,
632 FCollisionResponseParams::DefaultResponseParam);
633#endif
634
635 // Scale the pointcloud
636 HitResult.ImpactPoint *= 1e-2;
637
638 // Flip Y axis
639 HitResult.ImpactPoint.Y *= -1;
640
642 {
643 // Save Original FHitResult to separate array
644 FHitResult OriginalHitResultCopy = HitResult;
645 uint32 RGB = RGBDefault;
646 if (Semantic)
647 {
648 // If Lidar is set to Semantic, set point color
649 RGB = GetSemanticData(OriginalHitResultCopy);
650 }
651 const FVector& HitPoint = OriginalHitResultCopy.ImpactPoint;
652 FPointData point = { HitPoint.X, HitPoint.Y, HitPoint.Z, RGB };
653 PreProcessedHitImpactPoints[Channel].emplace_back(point);
654 }
655
656 bool BlockingHit = false;
658 {
659 // Check whether we "hit" snowflake by using LUAS formula
660 bool HitSnowFlake = LidarNoiseModel::CheckSnowflakeHit(HitResult, EndTrace, LidarBodyLocation, CurrentWeatherParameters);
661 BlockingHit = HitResult.bBlockingHit || HitSnowFlake;
662 }
663 else
664 {
665 BlockingHit = HitResult.IsValidBlockingHit();
666 }
667
668 return BlockingHit;
669}
670
671void ALidar::ResetRecordedHits(int32 MaxPointsPerChannel)
672{
673 if (!ClearContainers)
674 {
675 return;
676 }
677
678 // Before resetting visualize Lidar particles
680
682 {
683 // If we only send data at Lidar sensor rotation frequency
684 // reserve enough points to fill all points
686 }
687
689 {
690 for (auto& impactPoints : PreProcessedHitImpactPoints)
691 {
692 impactPoints.clear();
693 impactPoints.reserve(MaxPointsPerChannel);
694 }
695 }
696
697 PointsFlattened.clear();
698
699 for (auto& impactPoints : Points)
700 {
701 impactPoints.clear();
702 impactPoints.reserve(MaxPointsPerChannel);
703 }
704}
705
706void ALidar::SendDataToTopic(const std::vector<FPointData>& points)
707{
708 UTopic* Topic = GetROSTopic();
709
710 if (!IsROSConnected()
711 || !Topic
712 || !SendDataToROS
713 || !PointCloudMessage.IsValid()
714 || points.empty())
715 {
716 return;
717 }
718
719 const int pointSize = points.size();
720 size_t dataSize = pointSize * sizeof(FPointData);
721
722 PointCloudMessage->width = pointSize;
723 PointCloudMessage->row_step = dataSize;
724 PointCloudMessage->data_ptr = reinterpret_cast<uint8*>(const_cast<FPointData*>(points.data()));
725 Topic->Publish(PointCloudMessage);
726}
727
729{
731 {
732 return;
733 }
734
735 // Save point cloud to disk
736 FString Filename = FString::Printf(TEXT("%sLidar_%d.ply"), *FileSavePath, LidarSaves);
738
740 {
741 // Flatten 2D std::vector to 1D std::vector
742 const int32 ChannelCount = LidarParameters.Channels;
743 std::vector<FPointData> PointsWithOutNoiseModel;
744 size_t totalSize = 0;
745
746 for (int32 i = 0; i < ChannelCount; ++i)
747 {
748 totalSize += PreProcessedHitImpactPoints[i].size();
749 }
750 PointsWithOutNoiseModel.reserve(totalSize);
751
752 for (int32 i = 0; i < ChannelCount; ++i)
753 {
754 PointsWithOutNoiseModel.insert(PointsWithOutNoiseModel.end(), PreProcessedHitImpactPoints[i].begin(), PreProcessedHitImpactPoints[i].end());
755 }
756
757 // Save pointcloud to disk without any noise
758 FString PreprocessedFilename = FString::Printf(TEXT("%sLidar_%d_without_noise_model.ply"), *FileSavePath, LidarSaves);
759 UPointcloudUtilities::SaveVectorArrayAsPlyAsync(PreprocessedFilename, PointsWithOutNoiseModel);
760 }
761
762 ++LidarSaves;
763}
void AddRaycastLidar(ALidar *lidarPtr)
void RemoveRaycastLidar(ALidar *lidarPtr)
UNiagaraComponent * NiagaraComponent
Definition: Lidar.h:126
TMap< FName, FColor > SemanticColors
Definition: Lidar.h:242
bool ShootLaser(FHitResult &HitResult, const FVector &EndTrace, const FCollisionQueryParams &TraceParams, const FVector &LidarBodyLocation, const int32 Channel, const bool Semantic, const uint32 RGBDefault)
Definition: Lidar.cpp:611
FWeatherParameters CurrentWeatherParameters
Definition: Lidar.h:236
std::random_device RandomDevice
Definition: Lidar.h:253
uint32 GetSemanticData(const FHitResult &HitResult) const
Definition: Lidar.cpp:588
void Init(FLidarParameters parameters, bool SimulateSensor=true)
Definition: Lidar.cpp:37
float SensorHzFrequency
Definition: Lidar.h:262
std::vector< uint32_t > PointsPerChannel
Definition: Lidar.h:250
void SendData(const float DeltaTime)
Definition: Lidar.cpp:517
void AddLateralNoise(FHitResult &HitResult, const FVector &LidarBodyLocation, const bool UseHorizontalNoise)
Definition: Lidar.cpp:407
void ChangeLidarParameters(FLidarParameters newLidarParameters)
Definition: Lidar.cpp:572
bool CanSendData(const float DeltaTime)
Definition: Lidar.cpp:550
int LidarSaves
Definition: Lidar.h:267
std::vector< std::vector< FPointData > > PreProcessedHitImpactPoints
Definition: Lidar.h:246
void SaveDataToDisk()
Definition: Lidar.cpp:728
void ResetRecordedHits(int32 MaxPointsPerChannel)
Definition: Lidar.cpp:671
ALidar(const FObjectInitializer &ObjectInitializer)
Definition: Lidar.cpp:31
uint32 CachedSnowflakeColor
Definition: Lidar.h:271
std::vector< float > LaserAngles
Definition: Lidar.h:251
void UpdateLidarParticles(int NumberOfHits, const TArray< FVector > &HitPoints, const TArray< FLinearColor > &Colors)
Definition: Lidar.cpp:254
void SetParticleLifeTime(float ParticleLifeTime)
Definition: Lidar.cpp:265
std::mt19937 Generator
Definition: Lidar.h:254
bool LidarParametersChanged
Definition: Lidar.h:257
void AddProcessingToHitResult(FHitResult &HitResult, const FVector &LidarBodyLocation, const bool UseHorizontalNoise)
Definition: Lidar.cpp:388
void ChangeParameters()
Definition: Lidar.cpp:189
UWorld * World
Definition: Lidar.h:239
void OnWeatherChanged(FWeatherParameters WeatherParams)
Definition: Lidar.cpp:177
float SnowHeightOffset
Definition: Lidar.h:265
virtual void BeginPlay() override
Definition: Lidar.cpp:77
FLidarParameters TempLidarParameters
Definition: Lidar.h:234
void AddDistanceNoise(FHitResult &HitResult, const FVector &LidarBodyLocation)
Definition: Lidar.cpp:395
void SetNiagaraRendering(bool Enabled)
Definition: Lidar.cpp:239
std::vector< std::vector< FPointData > > Points
Definition: Lidar.h:247
std::vector< FPointData > SimulateRaycastLidar(const float DeltaTime)
Definition: Lidar.cpp:273
void VisualizeLidarParticles()
Definition: Lidar.cpp:461
uint32 CachedNoneColor
Definition: Lidar.h:269
bool ClearContainers
Definition: Lidar.h:259
bool NeedToSavePointCloudWithoutNoise
Definition: Lidar.h:256
std::vector< FPointData > PointsFlattened
Definition: Lidar.h:248
void SetVisualizeLidarParticles(bool Visualize)
Definition: Lidar.cpp:234
TSharedPtr< ROSMessages::sensor_msgs::PointCloud2 > PointCloudMessage
Definition: Lidar.h:244
void SendDataToTopic(const std::vector< FPointData > &points)
Definition: Lidar.cpp:706
void AddSnowTerrainAdjustment(FHitResult &HitResult, const FVector &LidarBodyLocation)
Definition: Lidar.cpp:432
bool SendDataEveryFrame
Definition: Lidar.h:258
float SensorHzTimer
Definition: Lidar.h:263
FLidarParameters LidarParameters
Definition: Lidar.h:232
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
Definition: Lidar.cpp:135
float CurrentHorizontalAngle
Definition: Lidar.h:261
static const FName NiagaraHitPoints
Definition: Sensor.h:358
bool CanSimulateSensor() const
Definition: Sensor.h:161
static void HideComponentForAllCameras(UPrimitiveComponent *PrimitiveComponent)
Definition: Sensor.cpp:255
static const FName NiagaraPointsFloat
Definition: Sensor.h:360
virtual void CreateDataSavePath()
Definition: Sensor.cpp:202
void SetSimulateSensor(bool SimulateSensor)
Definition: Sensor.h:151
UTopic * GetROSTopic() const
Definition: Sensor.h:141
bool IsROSConnected() const
Definition: Sensor.h:192
bool SendDataToROS
Definition: Sensor.h:344
static const FName NiagaraHitColors
Definition: Sensor.h:359
FString FileSavePath
Definition: Sensor.h:349
virtual void CreateROSTopic()
Definition: Sensor.cpp:146
static const FName NiagaraPointsInt
Definition: Sensor.h:357
static TMap< FString, FColor > GetSemanticColors()
Definition: Sensor.cpp:265
const FWeatherParameters & GetCurrentWeather() const
Definition: Weather.h:76
FLevelEventDelegate_WeatherChanged OnWeatherChanged
Definition: Weather.h:85
static bool CheckSnowflakeHit(FHitResult &HitInfo, const FVector EndTrace, const FVector LidarLocation, const FWeatherParameters &WeatherParameters)
static void Log(const FString &Message, bool LogToTextFile=true, bool LogToROS=true)
static AWeather * GetWeatherActor(const UObject *WorldContextObject)
static ALidarManager * GetLidarManager(const UObject *WorldContextObject)
static void SaveVectorArrayAsPlyAsync(FString FullFileName, const std::vector< FPointData > points)
bool SendDataAtRotationFrequency
bool SavePointcloudWithoutNoiseModel
bool UseTerrainSnowHitAdjustment
float Z
Definition: PointData.h:19
float Y
Definition: PointData.h:17
float X
Definition: PointData.h:15
uint32 RGB
Definition: PointData.h:21