Agrarsense
PhysicsUtilities.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 "PhysicsUtilities.h"
7#include "GameFramework/Actor.h"
8#include "Engine/GameViewportClient.h"
9#include "Engine/OverlapResult.h"
10#include "Engine/HitResult.h"
11#include "Engine/EngineTypes.h"
12#include "Engine/Engine.h"
13#include "Engine/World.h"
14
15#if WITH_EDITOR
16#include "DrawDebugHelpers.h"
17#endif
18
19bool UPhysicsUtilities::SnapActorToGround(AActor* Actor, float StartZOffset, float EndZOffset)
20{
21 bool ActorPositionWasChanged = false;
22
23 if (!Actor)
24 {
25 return ActorPositionWasChanged;
26 }
27
28 UWorld* World = Actor->GetWorld();
29 if (World && Actor)
30 {
31 // Perform a line trace from a point above the actor's location to a point slightly below it
32 const FVector ActorLocation = Actor->GetActorLocation();
33 const FVector StartLocation = ActorLocation + FVector(0.0f, 0.0f, StartZOffset);
34 const FVector EndLocation = ActorLocation - FVector(0.0f, 0.0f, EndZOffset);
35 FHitResult HitResult;
36 FCollisionQueryParams QueryParams;
37 QueryParams.AddIgnoredActor(Actor);
38 bool Hits = World->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, ECC_WorldStatic, QueryParams);
39
40 if (Hits)
41 {
42 //Actor->SetActorLocation(HitResult.ImpactPoint);
43 Actor->TeleportTo(HitResult.ImpactPoint, Actor->GetActorRotation());
44 ActorPositionWasChanged = true;
45 }
46 }
47
48 return ActorPositionWasChanged;
49}
50
51bool UPhysicsUtilities::SnapActorAboveGround(AActor* Actor, float AboveOffset)
52{
53 if (!Actor)
54 {
55 return false;
56 }
57
58 // First snap actor to the ground
59 SnapActorToGround(Actor);
60
61 // Then add Z offset
62 const FVector CurrentActorLocation = Actor->GetActorLocation();
63 const FVector EndActorLocation = CurrentActorLocation + FVector(0.0f, 0.0f, AboveOffset);
64 Actor->TeleportTo(EndActorLocation, Actor->GetActorRotation());
65 //Actor->SetActorLocation(EndActorLocation);
66
67 return true;
68}
69
70bool UPhysicsUtilities::DoesTopPercentageMeshOverlap(AActor* Actor, const UStaticMesh* Mesh, float TopPercentage, ECollisionChannel CollisionChannel)
71{
72 if (!Actor)
73 {
74 return false;
75 }
76
77 UWorld* World = Actor->GetWorld();
78
79 if (World && Mesh)
80 {
81 FTransform Transform = Actor->GetActorTransform();
82 FVector ActorLocation = Transform.GetLocation();
83 FVector ActorScale = Transform.GetScale3D();
84
85 // Calculate the top extent of the mesh bounds
86 FVector MeshExtent = Mesh->GetBounds().BoxExtent;
87 FVector TopExtent = FVector(MeshExtent.X, MeshExtent.Y, MeshExtent.Z * TopPercentage);
88
89 FVector StartLocation = ActorLocation + FVector(0.0f, 0.0f, MeshExtent.Z);
90 FVector EndLocation = ActorLocation + FVector(0.0f, 0.0f, MeshExtent.Z * TopPercentage);
91
92 FHitResult HitResult;
93 FCollisionQueryParams QueryParams;
94 QueryParams.AddIgnoredActor(Actor);
95
96 // Perform a line trace to check for collisions in the top percentage of the mesh
97 return World->LineTraceSingleByChannel(HitResult, StartLocation, EndLocation, CollisionChannel, QueryParams);
98 }
99
100 return false;
101}
102
103bool UPhysicsUtilities::HasOverlappingActors(const UStaticMesh* StaticMesh, const FTransform& Transform)
104{
105 if (!StaticMesh)
106 {
107#if WITH_EDITOR
108 UE_LOG(LogTemp, Warning, TEXT("UPhysicsUtilities.cpp: StaticMesh is null!"));
109#endif
110 return false;
111 }
112
113 if (!GEngine || !GEngine->GameViewport)
114 {
115 return false;
116 }
117
118 TArray<FOverlapResult> OverlapResults;
119
120 UWorld* World = GEngine->GameViewport->GetWorld();
121 if (World)
122 {
123 FVector Location = Transform.GetLocation();
124 FQuat Rotation = Transform.GetRotation();
125 FVector Scale = Transform.GetScale3D();
126 FBox ActorBounds = StaticMesh->GetBoundingBox().TransformBy(FTransform(Rotation, Location, Scale));
127
128 World->OverlapMultiByObjectType(
129 OverlapResults,
130 Location,
131 Rotation,
132 FCollisionObjectQueryParams(ECC_WorldStatic),
133 FCollisionShape::MakeBox(ActorBounds.GetSize() * 0.5f)
134 );
135 }
136
137 return OverlapResults.Num() > 0;
138}
139
140TArray<AActor*> UPhysicsUtilities::FindOverlappingActorsInSphere(const FTransform& ActorTransform, float Radius, bool DebugVisualizeRadius)
141{
142 return FindOverlappingActorsOfClass<AActor>(ActorTransform, Radius, DebugVisualizeRadius);
143}
144
145bool UPhysicsUtilities::AlignTransformFromGroundInMeters(AActor* Actor, FTransform& InTransform)
146{
147 if (!GEngine || !GEngine->GameViewport)
148 {
149 return false;
150 }
151
152 UWorld* World = GEngine->GameViewport->GetWorld();
153 if (!World)
154 {
155 return false;
156 }
157
158 FVector Position = InTransform.GetLocation();
159 const float HeightAboveGroundMeters = Position.Z;
160
161 // TraceStart at X,Y position but higher up in the air to avoid going underground.
162 const FVector TraceStart = FVector(Position.X, Position.Y, 6000.0f);
163 const FVector TraceEnd = TraceStart - FVector(0, 0, 100000.0f);
164
165 FHitResult HitResult;
166 FCollisionQueryParams QueryParams;
167 QueryParams.AddIgnoredActor(Actor);
168
169 bool bHit = World->LineTraceSingleByChannel(HitResult, TraceStart, TraceEnd, ECC_GameTraceChannel6, QueryParams);
170 if (bHit)
171 {
172 Position.Z = HitResult.ImpactPoint.Z + (HeightAboveGroundMeters * 100.0f);
173 InTransform.SetLocation(Position);
174
175 return true;
176 }
177
178 return false;
179}
180
181template<typename T>
182TArray<T*> UPhysicsUtilities::FindOverlappingActorsOfClass(const FTransform& ActorTransform, float Radius, bool DebugVisualizeRadius)
183{
184 TArray<T*> OverlappingActors;
185
186 if (!GEngine || !GEngine->GameViewport)
187 {
188 return OverlappingActors;
189 }
190
191 UWorld* World = GEngine->GameViewport->GetWorld();
192
193 if (World)
194 {
195 TArray<FOverlapResult> OverlapResults;
196 FCollisionQueryParams Params;
197 Params.AddIgnoredActor(nullptr);
198 Params.bTraceComplex = false;
199 Params.bReturnPhysicalMaterial = false;
200
201 FCollisionObjectQueryParams ObjectQueryParams;
202 ObjectQueryParams.AddObjectTypesToQuery(ECC_WorldStatic);
203
204 bool bOverlap = World->OverlapMultiByObjectType(
205 OverlapResults,
206 ActorTransform.GetLocation(),
207 ActorTransform.GetRotation(),
208 ObjectQueryParams,
209 FCollisionShape::MakeSphere(Radius),
210 Params
211 );
212
213 if (bOverlap)
214 {
215 for (const FOverlapResult& OverlapResult : OverlapResults)
216 {
217 T* OverlappingActor = Cast<T>(OverlapResult.GetActor());
218 if (OverlappingActor)
219 {
220 OverlappingActors.Add(OverlappingActor);
221 }
222 }
223 }
224 }
225
226#if WITH_EDITOR
227 if (DebugVisualizeRadius)
228 {
229 DrawDebugSphere(World, ActorTransform.GetLocation(), Radius, 12, FColor::Green, false, 2.0f, 0, 1.0f);
230 }
231#endif
232
233 return OverlappingActors;
234}
static bool SnapActorAboveGround(AActor *Actor, float AboveOffset=50.0f)
static bool HasOverlappingActors(const UStaticMesh *StaticMesh, const FTransform &Transform)
static bool AlignTransformFromGroundInMeters(AActor *Actor, FTransform &InTransform)
static bool DoesTopPercentageMeshOverlap(AActor *Actor, const UStaticMesh *Mesh, float TopPercentage, ECollisionChannel CollisionChannel)
static TArray< T * > FindOverlappingActorsOfClass(const FTransform &ActorTransform, float Radius, bool DebugVisualizeRadius=false)
static TArray< AActor * > FindOverlappingActorsInSphere(const FTransform &ActorTransform, float Radius, bool DebugVisualizeRadius=false)
static bool SnapActorToGround(AActor *Actor, float StartZOffset=600.0f, float EndZOffset=600.0f)