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