Fixes for collision detection when placing stuff
This commit is contained in:
Binary file not shown.
@@ -11,6 +11,7 @@
|
|||||||
#include "../Public/EleriPlayerController.h"
|
#include "../Public/EleriPlayerController.h"
|
||||||
#include "../Public/MyCharacter.h"
|
#include "../Public/MyCharacter.h"
|
||||||
#include "Engine/OverlapResult.h"
|
#include "Engine/OverlapResult.h"
|
||||||
|
#include "Engine/StaticMeshActor.h"
|
||||||
#include "LandscapeProxy.h"
|
#include "LandscapeProxy.h"
|
||||||
|
|
||||||
// Sets default values for this component's properties
|
// Sets default values for this component's properties
|
||||||
@@ -60,6 +61,7 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
{
|
{
|
||||||
ForwardVec = PlayerController->PlayerCameraManager->GetCameraRotation().Vector();
|
ForwardVec = PlayerController->PlayerCameraManager->GetCameraRotation().Vector();
|
||||||
ForwardVec.Z = 0.f;
|
ForwardVec.Z = 0.f;
|
||||||
|
ForwardVec.Normalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer the per-actor tuning values, falling back to the component defaults.
|
// Prefer the per-actor tuning values, falling back to the component defaults.
|
||||||
@@ -76,16 +78,29 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
const FVector TargetScale = (MovingActor->ScaleOverride != FVector::ZeroVector) ? MovingActor->ScaleOverride : MovingActor->GetActorScale();
|
const FVector TargetScale = (MovingActor->ScaleOverride != FVector::ZeroVector) ? MovingActor->ScaleOverride : MovingActor->GetActorScale();
|
||||||
MovingActor->SetActorTransform(FTransform(MovingActor->GetActorRotation(), PosVector, TargetScale));
|
MovingActor->SetActorTransform(FTransform(MovingActor->GetActorRotation(), PosVector, TargetScale));
|
||||||
|
|
||||||
// Snap the object down onto the ground beneath it.
|
// Look for a surface to rest the object on. The probe starts above the object so it can
|
||||||
|
// also land on top of already placed actors (allowing objects to be stacked on each other)
|
||||||
|
// instead of only snapping down onto the landscape.
|
||||||
FCollisionObjectQueryParams QueryParams;
|
FCollisionObjectQueryParams QueryParams;
|
||||||
QueryParams.AddObjectTypesToQuery(ECC_WorldStatic);
|
QueryParams.AddObjectTypesToQuery(ECC_WorldStatic);
|
||||||
|
|
||||||
const FVector TraceEnd = MovingActor->GetActorLocation() + FVector(0.f, 0.f, -GroundTraceLength);
|
FCollisionQueryParams TraceParams;
|
||||||
|
TraceParams.AddIgnoredActor(MovingActor);
|
||||||
|
TraceParams.AddIgnoredActor(PlayerCharacter);
|
||||||
|
|
||||||
|
const FVector TraceStart = MovingActor->GetActorLocation() + FVector(0.f, 0.f, SurfaceProbeUpOffset);
|
||||||
|
const FVector TraceEnd = MovingActor->GetActorLocation() - FVector(0.f, 0.f, GroundTraceLength);
|
||||||
|
|
||||||
TArray<FHitResult> OutHits;
|
TArray<FHitResult> OutHits;
|
||||||
GetWorld()->LineTraceMultiByObjectType(OutHits, MovingActor->GetActorLocation(), TraceEnd, QueryParams);
|
GetWorld()->LineTraceMultiByObjectType(OutHits, TraceStart, TraceEnd, QueryParams, TraceParams);
|
||||||
|
|
||||||
MovingActor->bPlaceable = true;
|
MovingActor->bPlaceable = true;
|
||||||
MovingActor->IntersectingActors.Empty();
|
MovingActor->IntersectingActors.Empty();
|
||||||
|
|
||||||
|
// The closest valid hit is the highest surface beneath the probe: that is what the object
|
||||||
|
// should rest on, which may be the landscape or the top of another (static mesh) actor.
|
||||||
|
const FHitResult* SupportHit = nullptr;
|
||||||
|
double ClosestSupportDistSq = TNumericLimits<double>::Max();
|
||||||
for (const FHitResult& HitResult : OutHits)
|
for (const FHitResult& HitResult : OutHits)
|
||||||
{
|
{
|
||||||
AActor* HitActor = HitResult.GetActor();
|
AActor* HitActor = HitResult.GetActor();
|
||||||
@@ -93,16 +108,39 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
|
|
||||||
MovingActor->IntersectingActors.Add(HitActor);
|
MovingActor->IntersectingActors.Add(HitActor);
|
||||||
|
|
||||||
if (HitActor->IsA(ALandscapeProxy::StaticClass()))
|
// Only the landscape and static mesh actors are valid surfaces to rest on.
|
||||||
|
const bool bIsValidSupport = HitActor->IsA(ALandscapeProxy::StaticClass())
|
||||||
|
|| HitActor->GetClass()->IsChildOf(AStaticMeshActor::StaticClass());
|
||||||
|
if (!bIsValidSupport) continue;
|
||||||
|
|
||||||
|
const double HitDistSq = FVector::DistSquared(TraceStart, HitResult.ImpactPoint);
|
||||||
|
if (HitDistSq < ClosestSupportDistSq)
|
||||||
{
|
{
|
||||||
MovingActor->SetActorLocation(HitResult.ImpactPoint);
|
ClosestSupportDistSq = HitDistSq;
|
||||||
|
SupportHit = &HitResult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether the object overlaps anything that should block placement.
|
// The bounds of the mesh are used for placement instead of the actor bounds: the actor also
|
||||||
FVector Center, BoxExtent;
|
// holds the detection box and the interaction widget, and its pivot is not guaranteed to sit
|
||||||
MovingActor->GetActorBounds(false, Center, BoxExtent);
|
// at the center (or at the bottom) of the mesh.
|
||||||
BoxExtent *= OverlapBoxExtentMultiplier;
|
const FBoxSphereBounds MeshBounds = GetMovingActorMeshBounds();
|
||||||
|
|
||||||
|
if (SupportHit)
|
||||||
|
{
|
||||||
|
// Rest the mesh on the surface that was found. The pivot is not necessarily at the bottom
|
||||||
|
// of the mesh, so the surface point is shifted by the mesh' bottom offset, which is zero for
|
||||||
|
// the common case of a pivot sitting at the bottom of the mesh.
|
||||||
|
const double MeshBottomOffsetZ = (MeshBounds.Origin.Z - MeshBounds.BoxExtent.Z) - MovingActor->GetActorLocation().Z;
|
||||||
|
MovingActor->SetActorLocation(SupportHit->ImpactPoint - FVector(0.0, 0.0, MeshBottomOffsetZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the object overlaps anything that should block placement. The query box follows
|
||||||
|
// the mesh' bounds and is queried around the center of the mesh to match the detection box:
|
||||||
|
// querying around the pivot would shift the box whenever the pivot is not at the mesh' center,
|
||||||
|
// which can make the object collide with the very surface it is resting on.
|
||||||
|
const FVector Center = MeshBounds.Origin;
|
||||||
|
FVector BoxExtent = MeshBounds.BoxExtent * OverlapBoxExtentMultiplier;
|
||||||
if (!MovingActor->BoxExtentOverride.IsNearlyZero())
|
if (!MovingActor->BoxExtentOverride.IsNearlyZero())
|
||||||
{
|
{
|
||||||
BoxExtent = MovingActor->BoxExtentOverride;
|
BoxExtent = MovingActor->BoxExtentOverride;
|
||||||
@@ -122,18 +160,27 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
TArray<FOverlapResult> OverlapResults;
|
TArray<FOverlapResult> OverlapResults;
|
||||||
GetWorld()->OverlapMultiByObjectType(
|
GetWorld()->OverlapMultiByObjectType(
|
||||||
OverlapResults,
|
OverlapResults,
|
||||||
MovingActor->GetActorLocation(),
|
Center,
|
||||||
MovingActor->GetActorQuat(),
|
MovingActor->GetActorQuat(),
|
||||||
ObjectQueryParams,
|
ObjectQueryParams,
|
||||||
FCollisionShape::MakeBox(BoxExtent),
|
FCollisionShape::MakeBox(BoxExtent),
|
||||||
OverlapQueryParams
|
OverlapQueryParams
|
||||||
);
|
);
|
||||||
|
|
||||||
bool PlantableAreaFound = false;
|
TArray<AActor*> OverlappingActors;
|
||||||
|
MovingActor->OverlapBoxComponent->GetOverlappingActors(OverlappingActors);
|
||||||
for (const FOverlapResult& OverlapResult : OverlapResults)
|
for (const FOverlapResult& OverlapResult : OverlapResults)
|
||||||
{
|
{
|
||||||
AActor* OverlappingActor = OverlapResult.GetActor();
|
if (AActor* OverlappingActor = OverlapResult.GetActor())
|
||||||
if (!OverlappingActor) continue;
|
{
|
||||||
|
OverlappingActors.Add(OverlappingActor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlantableAreaFound = false;
|
||||||
|
for (const AActor* OverlappingActor : OverlappingActors)
|
||||||
|
{
|
||||||
|
if (!OverlappingActor || OverlappingActor == MovingActor) continue;
|
||||||
|
|
||||||
if (PlantableAreaActorClass == OverlappingActor->GetClass())
|
if (PlantableAreaActorClass == OverlappingActor->GetClass())
|
||||||
{
|
{
|
||||||
@@ -158,6 +205,34 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An object that rests on top of another object has its box lifted out of the plantable area it
|
||||||
|
// stands in, which would make stacking inside a plantable area impossible. Look below the object
|
||||||
|
// before rejecting the placement, using the same footprint extended downwards.
|
||||||
|
if (!PlantableAreaFound && PlantableAreaActorClass && PlantableAreaProbeDepth > 0.f)
|
||||||
|
{
|
||||||
|
const float ExtraDepth = PlantableAreaProbeDepth * 0.5f;
|
||||||
|
|
||||||
|
TArray<FOverlapResult> PlantableOverlapResults;
|
||||||
|
GetWorld()->OverlapMultiByObjectType(
|
||||||
|
PlantableOverlapResults,
|
||||||
|
Center - FVector(0.f, 0.f, ExtraDepth),
|
||||||
|
MovingActor->GetActorQuat(),
|
||||||
|
ObjectQueryParams,
|
||||||
|
FCollisionShape::MakeBox(BoxExtent + FVector(0.f, 0.f, ExtraDepth)),
|
||||||
|
OverlapQueryParams
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const FOverlapResult& OverlapResult : PlantableOverlapResults)
|
||||||
|
{
|
||||||
|
const AActor* OverlappingActor = OverlapResult.GetActor();
|
||||||
|
if (OverlappingActor && PlantableAreaActorClass == OverlappingActor->GetClass())
|
||||||
|
{
|
||||||
|
PlantableAreaFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!PlantableAreaFound)
|
if (!PlantableAreaFound)
|
||||||
{
|
{
|
||||||
MovingActor->bPlaceable = false;
|
MovingActor->bPlaceable = false;
|
||||||
@@ -170,6 +245,23 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
|||||||
MovingActor->K2_TogglePlaceableEffects(MovingActor->bPlaceable);
|
MovingActor->K2_TogglePlaceableEffects(MovingActor->bPlaceable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FBoxSphereBounds UMoveActorsComponent::GetMovingActorMeshBounds() const
|
||||||
|
{
|
||||||
|
if (!IsValid(MovingActor))
|
||||||
|
{
|
||||||
|
return FBoxSphereBounds(FVector::ZeroVector, FVector::ZeroVector, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalcBounds returns the world space bounds of the mesh, so the actor scale and the actor
|
||||||
|
// rotation are taken into account. Those bounds are not necessarily centered on the pivot.
|
||||||
|
if (const UStaticMeshComponent* MeshComponent = MovingActor->GetStaticMeshComponent())
|
||||||
|
{
|
||||||
|
return MeshComponent->CalcBounds(MeshComponent->GetComponentTransform());
|
||||||
|
}
|
||||||
|
|
||||||
|
return FBoxSphereBounds(MovingActor->GetActorLocation(), FVector::ZeroVector, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
void UMoveActorsComponent::ToggleGridMovement()
|
void UMoveActorsComponent::ToggleGridMovement()
|
||||||
{
|
{
|
||||||
bGridMovementActive = !bGridMovementActive;
|
bGridMovementActive = !bGridMovementActive;
|
||||||
|
|||||||
@@ -59,6 +59,18 @@ public:
|
|||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement")
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement")
|
||||||
float GroundTraceLength = 1000.f;
|
float GroundTraceLength = 1000.f;
|
||||||
|
|
||||||
|
/** How far above the hovered position the downward surface probe starts. A larger value
|
||||||
|
* lets the object snap on top of taller stacks of objects instead of only onto the landscape. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement")
|
||||||
|
float SurfaceProbeUpOffset = 500.f;
|
||||||
|
|
||||||
|
/** How far below the object's own overlap box a plantable area is still accepted as the area the
|
||||||
|
* object is being placed in. An object that rests on top of another object has its box lifted
|
||||||
|
* out of the plantable area's volume, so a value larger than 0 is what keeps stacked objects
|
||||||
|
* placeable. Use 0 to only accept plantable areas that the object's own box overlaps. */
|
||||||
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "0.0"))
|
||||||
|
float PlantableAreaProbeDepth = 1000.f;
|
||||||
|
|
||||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement", meta = (ClampMin = "0.0", ClampMax = "1.0"))
|
||||||
float OverlapBoxExtentMultiplier = 0.9f;
|
float OverlapBoxExtentMultiplier = 0.9f;
|
||||||
|
|
||||||
@@ -89,6 +101,11 @@ protected:
|
|||||||
UPROPERTY()
|
UPROPERTY()
|
||||||
bool bGridMovementActive;
|
bool bGridMovementActive;
|
||||||
|
|
||||||
|
/** World bounds of the mesh of the object being moved, or a zero sized box at its location when
|
||||||
|
* it has no static mesh. The actor bounds are not used for placement because they also contain
|
||||||
|
* the detection box and the interaction widget, which do not match the mesh that is placed. */
|
||||||
|
FBoxSphereBounds GetMovingActorMeshBounds() const;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// Called every frame
|
// Called every frame
|
||||||
|
|||||||
Reference in New Issue
Block a user