Agrarsense
PIDDrone.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 "PIDDrone.h"
7
12
17#include "ROSIntegration/Classes/RI/Topic.h"
18
19#include "Camera/CameraComponent.h"
20#include "Field/FieldSystemNodes.h"
21#include "NiagaraComponent.h"
22#include "Algo/Reverse.h"
23
24#if WITH_EDITOR
25#include "DrawDebugHelpers.h"
26#endif
27
29{
30 PrimaryActorTick.bCanEverTick = true;
31 InteractableName = NSLOCTEXT("Agrarsense", "DroneInteractableName", "Drone");
32}
33
35{
36//#if WITH_EDITOR
37// UE_LOG(LogTemp, Warning, TEXT("Parameters Changed!"));
38//#endif
39
40 DroneParameters = newParameters;
41
43 {
44 FString OverlapSensorID = ActorID + "/inner_overlap";
45
46 FSensorSpawnParameters SpawnParams;
47 SpawnParams.Transform = GetActorTransform();
48 SpawnParams.SensorIdentifier = OverlapSensorID;
49 SpawnParams.SensorName = "overlap";
50 SpawnParams.SimulateSensor = true;
51 SpawnParams.Parent = this;
52
53 FOverlapSensorParameters SensorParams;
54 SensorParams.OwningActor = this;
55 SensorParams.AllChannels = true;
57 SensorParams.RelativePosition = FVector(0.0f, 0.0f, 0.0f);
58
59 InnerOverlapSensor = USensorFactory::SpawnOverlapSensor(SpawnParams, SensorParams);
60
62 {
63 InnerOverlapSensor->AttachToComponent(DroneSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform);
64 }
65 }
67 {
68 InnerOverlapSensor->Destroy();
69 InnerOverlapSensor = nullptr;
70 }
71
73 {
74 DroneSkeletalMesh->SetCollisionProfileName(TEXT("DroneNoCollision"));
75 }
76
77 if (OverlapSensor)
78 {
80
81 const float BoundsSizeMeters = DroneParameters.OverlapRadiusMeters;
82 OverlapSensor->SetOverlapBounds(FVector(BoundsSizeMeters, BoundsSizeMeters, BoundsSizeMeters));
83
84 }
85
87 {
89
90 const float BoundsSizeMeters = DroneParameters.InnerOverlapRadiusMeters;
91 InnerOverlapSensor->SetOverlapBounds(FVector(BoundsSizeMeters, BoundsSizeMeters, BoundsSizeMeters));
92 }
93}
94
96{
97 Super::BeginPlay();
98
99 World = GetWorld();
100
101 ROSMessage.Reset();
102 ROSMessage = MakeShared<ROSMessages::std_msgs::Float32>();
103
104 CreateTopic();
105
106 // These should be setup in BP_Drone_PID blueprint.
107 DroneSkeletalMesh = Cast<USkeletalMeshComponent>(GetComponentByClass(USkeletalMeshComponent::StaticClass()));
108 PositionMesh = Cast<UStaticMeshComponent>(GetComponentByClass(UStaticMeshComponent::StaticClass()));
109
110 StartingPosition = GetActorLocation();
111 FTransform ActorTransform = GetActorTransform();
112
114 {
115 // Create Transform sensor for forwarder and harvester
116
117 FString VehicleTransformSensorID = ActorID + "/transform";
118
119 FSensorSpawnParameters TransformSensorSpawnParams;
120 TransformSensorSpawnParams.Transform = GetActorTransform();
121 TransformSensorSpawnParams.SensorIdentifier = VehicleTransformSensorID;
122 TransformSensorSpawnParams.SensorName = "transform";
123 TransformSensorSpawnParams.SimulateSensor = true;
124 TransformSensorSpawnParams.Parent = this;
125
126 FTransformSensorParameters TransformSensorParams;
127 TransformSensorParams.SaveTransformDataToDisk = true;
128 TransformSensorParams.OwningActor = this;
129 TransformSensorParams.UseOwningActorTransform = false;
130 TransformSensorParams.PrimitiveComponent = Cast<UPrimitiveComponent>(DroneSkeletalMesh);
131
132 TransformSensor = USensorFactory::SpawnTransformSensor(TransformSensorSpawnParams, TransformSensorParams);
133 }
134
135 if (!OverlapSensor)
136 {
137 // Create OverlapSensor
138
139 FString OverlapSensorID = ActorID + "/overlap";
140
141 FSensorSpawnParameters OverlapSensorSpawnParams;
142 OverlapSensorSpawnParams.Transform = GetActorTransform();
143 OverlapSensorSpawnParams.SensorIdentifier = OverlapSensorID;
144 OverlapSensorSpawnParams.SensorName = "overlap";
145 OverlapSensorSpawnParams.SimulateSensor = true;
146 OverlapSensorSpawnParams.Parent = this;
147
148 FOverlapSensorParameters OverlapSensorParams;
149 OverlapSensorParams.OwningActor = this;
150 OverlapSensorParams.Size = FVector(25.0f, 25.0f, 25.0f);
151 OverlapSensorParams.RelativePosition = FVector(0.0f, 0.0f, 0.0f);
152
153 OverlapSensor = USensorFactory::SpawnOverlapSensor(OverlapSensorSpawnParams, OverlapSensorParams);
154
155 if (OverlapSensor)
156 {
157 OverlapSensor->AttachToComponent(DroneSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform);
158 }
159 }
160
162 {
163 // Attach rain/snowfall niagara component to drone skeletal mesh.
164 NiagaraComponent->AttachToComponent(DroneSkeletalMesh, FAttachmentTransformRules::KeepRelativeTransform);
165 }
166
167 // Apply default parameters
169}
170
171void APIDDrone::EndPlay(const EEndPlayReason::Type EndPlayReason)
172{
173 Super::EndPlay(EndPlayReason);
174
175 if (TransformSensor)
176 {
177 TransformSensor->Destroy();
178 TransformSensor = nullptr;
179 }
180
182 {
183 InnerOverlapSensor->Destroy();
184 InnerOverlapSensor = nullptr;
185 }
186
187 ROSMessage.Reset();
188 DestroyTopic();
189}
190
191void APIDDrone::Tick(float DeltaTime)
192{
193 Super::Tick(DeltaTime);
194
195 if (!IsVehicleInGarage())
196 {
197 AutoPilot(DeltaTime);
199
200#if WITH_EDITOR
201 if (drawDebugPoints)
202 {
204 }
205#endif
206
207 }
208}
209
210void APIDDrone::AutoPilot(const float DeltaTime)
211{
213 {
214 return;
215 }
216
218}
219
221{
222 TArray<FTransform> dronePoints = DroneParameters.Points;
223
224 dronePath.Add(DroneSkeletalMesh->GetComponentTransform());
225
226 for (int32 i = 0; i < dronePoints.Num(); i++)
227 {
228 if (World)
229 {
230 //UE_LOG(LogTemp, Warning, TEXT("Drone point %d: %s"), i, *dronePoints[i].GetLocation().ToString());
231 DrawDebugSphere(World, dronePoints[i].GetLocation(), 5.0f, 25, FColor::Red, false);
232 if (i < dronePoints.Num() - 1)
233 {
234 FVector nextPointLocation = dronePoints[i + 1].GetLocation();
235 DrawDebugLine(World, dronePoints[i].GetLocation(), nextPointLocation, FColor::Blue, false, 0.0f, 0, 5.0f);
236 }
237 }
238 }
239
240 for (int32 i = 0; i < dronePath.Num(); i++)
241 {
242 if (i < dronePath.Num() - 1)
243 {
244 FVector nextPointLocation = dronePath[i + 1].GetLocation();
245 DrawDebugLine(World, dronePath[i].GetLocation(), nextPointLocation, FColor::Green, false, 0.0f, 0, 5.0f);
246 }
247 }
248}
249
251{
252 // Should trigger EndPlay?
253
255 if (SimulationLevelManager)
256 {
257 AVehicle* controlledVehicle = SimulationLevelManager->GetManuallyControlledVehicle();
258
259 if (controlledVehicle == this)
260 {
261 SimulationLevelManager->CeaseManualControlOfVehicle();
262 }
263 }
264
265 passedWaypoints = 0;
266 Destroy();
267}
268
269
271{
272 // TODO: Check if in garage
273 // include drone rotation to point
274
275 // Don't try to fly if no waypoints
276 if (DroneParameters.Points.Num() == 0 && IsRoaming())
277 {
279 }
280
281 FVector waypoint = GetCurrentWaypointTarget();
282 FVector currentlocation = DroneSkeletalMesh->GetRelativeTransform().GetLocation();
283
284 if (PositionMesh)
285 {
286 // Doesn't work
287 PositionMesh->SetWorldLocation(waypoint);
288 }
289#if WITH_EDITOR
290 else
291 {
292 UE_LOG(LogTemp, Warning, TEXT("Desired location mesh not found"));
293 //DrawDebugLine(World, currentlocation, waypoint, FColor::Red, false, 0.0f, 0.0f, 5.0f);
294 }
295#endif
296 distanceToNextPoint = FVector2f::Distance(FVector2f(waypoint.X, waypoint.Y), FVector2f(currentlocation.X, currentlocation.Y));
297 if (distanceToNextPoint < 100)
298 {
299 waypointReached = true;
300 if (passedWaypoints != DroneParameters.Points.Num() - 1)
301 {
302#if WITH_EDITOR
303 UE_LOG(LogTemp, Warning, TEXT("Changing waypoint from %i to %i"), passedWaypoints, passedWaypoints + 1);
304#endif
305
307 }
308 else
309 {
311 {
313 passedWaypoints = 0;
314 }
315 else
316 {
318 {
321 OnDroneFinished.Broadcast(DroneParameters.Points.Last());
322 break;
325 passedWaypoints = 0;
326 break;
328 passedWaypoints = 0;
329 break;
331 Algo::Reverse(DroneParameters.Points);
332 passedWaypoints = 0;
333 break;
335 OnDroneFinished.Broadcast(DroneParameters.Points.Last());
336 if (!GetWorld()->GetTimerManager().IsTimerActive(DestroyHandle))
337 {
339 GetWorld()->GetTimerManager().SetTimer(DestroyHandle, this, &APIDDrone::HandleDestroy, 5.0f, false);
340 }
341 break;
342 default:
343 break;
344 }
345 }
346 }
347 }
348}
349
350void APIDDrone::MoveDroneToPosition(const FTransform Transform)
351{
352 if (DroneParameters.Points.Num() > 0)
353 {
354 DroneParameters.Points.Empty();
356 }
357 else
358 {
360 }
361}
362
364{
365 return GetCurrentWaypointTarget_Transform().GetLocation();
366}
367
369{
371 {
372 return DroneSkeletalMesh->GetRelativeTransform();
373 }
374
376}
377
378void APIDDrone::AssignRoamingPoints(const TArray<FTransform> Points)
379{
381
382 for (const FTransform& point : Points)
383 {
384 WayPoints.Add(point.GetLocation());
385 }
386}
387
388void APIDDrone::SetDroneRotation(USkeletalMeshComponent* target, FRotator rotator)
389{
390 if (!target)
391 {
392 return;
393 }
394
395 FRotator currentRotation = target->GetRelativeRotation();
396 FRotator rotationDifference = rotator - currentRotation;
397
398 target->AddRelativeRotation(rotationDifference, false, nullptr, ETeleportType::TeleportPhysics);
399
400 // Prevent locking up when using interp
401 if (FMath::Abs(rotator.Pitch) < 0.001f)
402 {
403 rotator.Pitch = 0.0f;
404 }
405 if (FMath::Abs(rotator.Yaw) < 0.001f)
406 {
407 rotator.Yaw = 0.0f;
408 }
409 if (FMath::Abs(rotator.Roll) < 0.001f)
410 {
411 rotator.Roll = 0.0f;
412 }
413
414 // Corrected rotation
415 target->SetRelativeRotation(rotator, false, nullptr, ETeleportType::TeleportPhysics);
416}
417
419{
420 if (!DroneSkeletalMesh || !ROSTopic || !ROSMessage.IsValid())
421 {
422 return;
423 }
424
425 FVector Start = DroneSkeletalMesh->GetComponentLocation();
426 FVector End = Start - FVector(0, 0, 10000.0f);
427
428 FCollisionQueryParams TraceParams = FCollisionQueryParams(FName(TEXT("UpdateGroundHeight")), true, this);
429 TraceParams.bReturnPhysicalMaterial = false;
430 TraceParams.bTraceComplex = false;
431
432 FHitResult HitResult;
433
434#ifdef ParallelLineTraceSingleByChannel_EXISTS
435 // Defined and implemented in our AGRARSENSE Unreal Engine fork
436 // This LineTrace method doesn't block the physics scene which improves linetrace performance.
437 World->ParallelLineTraceSingleByChannel(
438 HitResult,
439 Start,
440 End,
441 ECC_Visibility,
442 TraceParams,
443 FCollisionResponseParams::DefaultResponseParam);
444#else
445 // If not using our fork of the engine, use default LineTrace method.
446 World->LineTraceSingleByChannel(
447 HitResult,
448 Start,
449 End,
450 ECC_Visibility,
451 TraceParams,
452 FCollisionResponseParams::DefaultResponseParam);
453#endif
454
455 if (HitResult.IsValidBlockingHit())
456 {
457 float Height = (Start.Z - HitResult.ImpactPoint.Z) / 100.0f;
458 float HeightRounded = FMath::RoundToFloat(Height * 1000.0f) / 1000.0f;
459
460 if (!FMath::IsNearlyEqual(DroneHeightFromGround, HeightRounded, 0.005f))
461 {
462 DroneHeightFromGround = HeightRounded;
463
465 ROSTopic->Publish(ROSMessage);
466
467 //FString HitActorName = HitResult.GetActor() ? HitResult.GetActor()->GetName() : TEXT("None");
468 //UE_LOG(LogTemp, Log, TEXT("Drone is %f meters above ground. Hit object: %s"), DroneHeightFromGround, *HitActorName);
469 }
470 }
471}
472
473TArray<FTransform> APIDDrone::GenerateRoamingPoints(float radius, int32 roamingPoints)
474{
475 TArray<FTransform> generatedRoamingPoints;
476 generatedRoamingPoints.Reserve(roamingPoints);
477
478 FVector currentPosition = GetTransform().GetLocation();
479
480 FVector min = currentPosition - FVector(radius / 2, radius / 2, 0);
481 FVector max = currentPosition + FVector(radius / 2, radius / 2, 0);
482
483 for (int32 i = 0; i < roamingPoints; i++)
484 {
485 FTransform randomPoint;
486 randomPoint.SetLocation(FVector(FMath::RandRange(min.X, max.X), FMath::RandRange(min.Y, max.Y), 5000));
487
488 generatedRoamingPoints.Add(randomPoint);
489#if WITH_EDITOR
490 UE_LOG(LogTemp, Warning, TEXT("Waypoint %i: (%s)"), i, *randomPoint.GetLocation().ToString());
491#endif
492 }
493
494 return generatedRoamingPoints;
495}
496
498{
499 switch (ROSState)
500 {
502 CreateTopic();
503 break;
504
506 DestroyTopic();
507 break;
508 }
509}
510
512{
513 if (ROSTopic)
514 {
515 return;
516 }
517
518 UROSIntegrationGameInstance* ROSInstance = UAgrarsenseStatics::GetROSGameInstance(GetWorld());
519 if (ROSInstance && ROSInstance->IsROSConnected())
520 {
521 ROSTopic = NewObject<UTopic>(UTopic::StaticClass());
522 if (ROSTopic)
523 {
524
525 FString TopicName = FString::Printf(TEXT("/agrarsense/out/vehicles/%s/height"), *GetActorID_Implementation());
526 //UE_LOG(LogTemp, Warning, TEXT("TopicName is %s"), *TopicName);
527
528 ROSTopic->Init(ROSInstance->ROSIntegrationCore, TopicName, "std_msgs/Float32");
529 ROSTopic->Advertise();
530 }
531 }
532}
533
534float APIDDrone::GetYawRotationDifference(USkeletalMeshComponent* DroneMesh, UStaticMeshComponent* DesiredLocation)
535{
536 if (!DroneMesh || !DesiredLocation)
537 {
538 return 0.0f;
539 }
540
541 FVector ToTarget = DesiredLocation->GetComponentLocation() - DroneMesh->GetComponentLocation();
542 FRotator LookRotation = ToTarget.Rotation();
543
544 float CurrentYaw = DroneMesh->GetComponentRotation().Yaw;
545 float DesiredYaw = LookRotation.Yaw;
546
547 float DeltaYaw = FRotator::NormalizeAxis(DesiredYaw - CurrentYaw);
548 return DeltaYaw;
549}
550
552{
553 if (ROSTopic)
554 {
555 ROSTopic->Unadvertise();
556 ROSTopic->Unsubscribe();
557 ROSTopic->MarkAsDisconnected();
558 ROSTopic->ConditionalBeginDestroy();
559 ROSTopic = nullptr;
560 }
561}
EROSState
Definition: ROSState.h:16
void SetVisualizeOverlapArea(bool Visualize)
void SetOverlapBounds(const FVector &NewSize)
TSharedPtr< ROSMessages::std_msgs::Float32 > ROSMessage
Definition: PIDDrone.h:198
void CreateTopic()
Definition: PIDDrone.cpp:511
bool waypointReached
Definition: PIDDrone.h:269
TArray< FVector > WayPoints
Definition: PIDDrone.h:264
void AssignRoamingPoints(const TArray< FTransform > Points)
Definition: PIDDrone.cpp:378
void SetDroneRotation(USkeletalMeshComponent *target, FRotator rotator)
Definition: PIDDrone.cpp:388
FVector GetCurrentWaypointTarget()
Definition: PIDDrone.cpp:363
float DroneHeightFromGround
Definition: PIDDrone.h:279
void MoveDroneToPosition(const FTransform Transform)
Override all drone roaming points and continue towards this position.
Definition: PIDDrone.cpp:350
UWorld * World
Definition: PIDDrone.h:250
void AutoPilot(const float DeltaTime)
Definition: PIDDrone.cpp:210
int32 passedWaypoints
Definition: PIDDrone.h:277
FVector StartingPosition
Definition: PIDDrone.h:252
virtual void BeginPlay() override
Definition: PIDDrone.cpp:95
void UpdateGroundHeight()
Definition: PIDDrone.cpp:418
void HandleDestroy()
Definition: PIDDrone.cpp:250
void ClearWaypoints()
Definition: PIDDrone.h:115
UTopic * ROSTopic
Definition: PIDDrone.h:196
void SetFlightpath()
Called in tick function for drone roaming through points.
Definition: PIDDrone.cpp:270
void ROSBridgeStateChanged(EROSState ROSState) override
Definition: PIDDrone.cpp:497
float GetYawRotationDifference(USkeletalMeshComponent *DroneSkeletalMesh, UStaticMeshComponent *DesiredLocation)
Get wanted waypoint target rotation.
Definition: PIDDrone.cpp:534
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
Definition: PIDDrone.cpp:171
float distanceToNextPoint
Definition: PIDDrone.h:281
void DrawDebugPoints()
Definition: PIDDrone.cpp:220
void DestroyTopic()
Definition: PIDDrone.cpp:551
FTransform GetCurrentWaypointTarget_Transform()
Definition: PIDDrone.cpp:368
FDroneParameters DroneParameters
Definition: PIDDrone.h:244
bool IsRoaming() const
Definition: PIDDrone.h:227
AOverlapSensor * InnerOverlapSensor
Definition: PIDDrone.h:261
FDroneFinished OnDroneFinished
Definition: PIDDrone.h:61
TArray< FTransform > dronePath
Definition: PIDDrone.h:283
bool drawDebugPoints
Definition: PIDDrone.h:275
UStaticMeshComponent * PositionMesh
Definition: PIDDrone.h:258
USkeletalMeshComponent * DroneSkeletalMesh
Definition: PIDDrone.h:255
FTimerHandle DestroyHandle
Definition: PIDDrone.h:285
void ChangeDroneParameters(const FDroneParameters &newParameters)
Definition: PIDDrone.cpp:34
TArray< FTransform > GenerateRoamingPoints(float radius, int32 roamingPoints)
Generates a roadming points array for the drone in radius.
Definition: PIDDrone.cpp:473
virtual void Tick(float DeltaTime) override
Definition: PIDDrone.cpp:191
virtual FString GetActorID_Implementation() const override
Definition: Vehicle.h:204
FText InteractableName
Definition: Vehicle.h:263
bool IsVehicleInGarage() const
Definition: Vehicle.h:133
FString ActorID
Definition: Vehicle.h:310
AOverlapSensor * OverlapSensor
Definition: Vehicle.h:296
ATransformSensor * TransformSensor
Definition: Vehicle.h:293
UNiagaraComponent * NiagaraComponent
Definition: Vehicle.h:299
static UROSIntegrationGameInstance * GetROSGameInstance(const UObject *WorldContextObject)
static ASimulationLevelManager * GetSimulationLevelManager(const UObject *WorldContextObject)
static ATransformSensor * SpawnTransformSensor(const FSensorSpawnParameters SpawnParameters, FTransformSensorParameters SensorParameters)
static AOverlapSensor * SpawnOverlapSensor(const FSensorSpawnParameters SpawnParameters, FOverlapSensorParameters SensorParameters)
float InnerOverlapRadiusMeters
EDroneEndAction DroneEndAction
EDroneAction DroneAction
TArray< FTransform > Points
UPrimitiveComponent * PrimitiveComponent