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/MyCharacter.h"
|
||||
#include "Engine/OverlapResult.h"
|
||||
#include "Engine/StaticMeshActor.h"
|
||||
#include "LandscapeProxy.h"
|
||||
|
||||
// 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.Z = 0.f;
|
||||
ForwardVec.Normalize();
|
||||
}
|
||||
|
||||
// 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();
|
||||
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;
|
||||
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;
|
||||
GetWorld()->LineTraceMultiByObjectType(OutHits, MovingActor->GetActorLocation(), TraceEnd, QueryParams);
|
||||
GetWorld()->LineTraceMultiByObjectType(OutHits, TraceStart, TraceEnd, QueryParams, TraceParams);
|
||||
|
||||
MovingActor->bPlaceable = true;
|
||||
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)
|
||||
{
|
||||
AActor* HitActor = HitResult.GetActor();
|
||||
@@ -93,16 +108,39 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
||||
|
||||
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.
|
||||
FVector Center, BoxExtent;
|
||||
MovingActor->GetActorBounds(false, Center, BoxExtent);
|
||||
BoxExtent *= OverlapBoxExtentMultiplier;
|
||||
// The bounds of the mesh are used for placement instead of the actor bounds: the actor also
|
||||
// holds the detection box and the interaction widget, and its pivot is not guaranteed to sit
|
||||
// at the center (or at the bottom) of the mesh.
|
||||
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())
|
||||
{
|
||||
BoxExtent = MovingActor->BoxExtentOverride;
|
||||
@@ -122,18 +160,27 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
||||
TArray<FOverlapResult> OverlapResults;
|
||||
GetWorld()->OverlapMultiByObjectType(
|
||||
OverlapResults,
|
||||
MovingActor->GetActorLocation(),
|
||||
Center,
|
||||
MovingActor->GetActorQuat(),
|
||||
ObjectQueryParams,
|
||||
FCollisionShape::MakeBox(BoxExtent),
|
||||
OverlapQueryParams
|
||||
);
|
||||
|
||||
bool PlantableAreaFound = false;
|
||||
|
||||
TArray<AActor*> OverlappingActors;
|
||||
MovingActor->OverlapBoxComponent->GetOverlappingActors(OverlappingActors);
|
||||
for (const FOverlapResult& OverlapResult : OverlapResults)
|
||||
{
|
||||
AActor* OverlappingActor = OverlapResult.GetActor();
|
||||
if (!OverlappingActor) continue;
|
||||
if (AActor* OverlappingActor = OverlapResult.GetActor())
|
||||
{
|
||||
OverlappingActors.Add(OverlappingActor);
|
||||
}
|
||||
}
|
||||
|
||||
bool PlantableAreaFound = false;
|
||||
for (const AActor* OverlappingActor : OverlappingActors)
|
||||
{
|
||||
if (!OverlappingActor || OverlappingActor == MovingActor) continue;
|
||||
|
||||
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)
|
||||
{
|
||||
MovingActor->bPlaceable = false;
|
||||
@@ -170,6 +245,23 @@ void UMoveActorsComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
|
||||
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()
|
||||
{
|
||||
bGridMovementActive = !bGridMovementActive;
|
||||
|
||||
@@ -59,6 +59,18 @@ public:
|
||||
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Placement")
|
||||
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"))
|
||||
float OverlapBoxExtentMultiplier = 0.9f;
|
||||
|
||||
@@ -89,6 +101,11 @@ protected:
|
||||
UPROPERTY()
|
||||
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:
|
||||
// Called every frame
|
||||
|
||||
Reference in New Issue
Block a user