Grid Placement System
Library Components
PlacementValidator

PlacementValidator

The PlacementValidator is a specialized library component that handles collision detection and placement validity validation for the Grid Placement System. It provides sophisticated collision checking with oriented bounding boxes, surface validation, and intelligent filtering to determine whether objects can be placed at specific locations.

Overview

PlacementValidator performs comprehensive placement validation by checking for collisions, validating surface compatibility, and filtering objects that should or shouldn't block placement. It uses advanced oriented bounding box collision detection to accurately handle rotated objects and provides detailed debugging capabilities.

Key Features

  • Oriented Collision Detection - Accurate collision checking for rotated objects
  • Surface Validation - Ensures objects are placed on appropriate surfaces
  • Intelligent Filtering - Distinguishes between blocking and non-blocking objects
  • Precision Mode Support - Bypasses validation when in precision mode
  • Placement Type Awareness - Different validation rules for Floor/Wall/Roof placement
  • Debug Visualization - Comprehensive debug output and visual feedback
  • Performance Optimized - Efficient collision detection with minimal overhead

Enums

EPlacementValidity

UENUM(BlueprintType)
enum class EPlacementValidity : uint8
{
    Valid       UMETA(DisplayName = "Valid Placement"),
    Invalid     UMETA(DisplayName = "Invalid Placement"),
    Unknown     UMETA(DisplayName = "Unknown")
};

Core Methods

Initialization

Initialize

void Initialize(UWorld* World);

Initializes the validator with a world context.

Parameters:

  • World - The world context for collision detection

Validation Methods

CheckPlacementValidity

EPlacementValidity CheckPlacementValidity(
    const FVector& Location,
    const FRotator& Rotation,
    AActor* PreviewActor,
    bool bIsInPrecisionMode,
    EPlacementType CurrentPlacementType,
    AActor* OverlayActor = nullptr,
    AActor* EditingTarget = nullptr
) const;

Main validation method that determines if placement is valid at a given location.

Parameters:

  • Location - World position to validate
  • Rotation - Object rotation at placement location
  • PreviewActor - The preview actor being placed
  • bIsInPrecisionMode - Whether precision mode is active
  • CurrentPlacementType - Type of placement (Floor/Wall/Roof)
  • OverlayActor - Overlay actor to ignore during collision
  • EditingTarget - Object being edited (ignored during collision)

Returns:

  • EPlacementValidity::Valid - Safe to place object
  • EPlacementValidity::Invalid - Collision or invalid placement
  • EPlacementValidity::Unknown - Unable to determine validity

Precision Mode Behavior:

if (bIsInPrecisionMode)
{
    return EPlacementValidity::Valid; // Always valid in precision mode
}

IsLocationOccupied

bool IsLocationOccupied(
    const FVector& Location,
    const FVector2D& ObjectSize,
    AActor* PreviewActor,
    AActor* OverlayActor,
    AActor* EditingTarget,
    EPlacementType CurrentPlacementType
) const;

Detailed collision detection with oriented bounding box support.

Key Features:

  • Uses oriented bounding box collision for rotated objects
  • Applies placement-specific collision margins
  • Intelligent actor filtering based on placement type
  • Accounts for object bounds center offset

Object Classification

ShouldActorBlockPlacement

bool ShouldActorBlockPlacement(AActor* Actor, EPlacementType CurrentPlacementType) const;

Determines if a specific actor should block placement.

Blocking Logic:

  1. Always blocks: Pawns (players, NPCs)
  2. Always blocks: Objects implementing IPlaceableObjectInterface
  3. Context-sensitive: Placement surfaces
    • Matching surfaces (Floor on Floor): Allow
    • Different surfaces (Floor on Wall): Block
  4. Always blocks: Static meshes with collision enabled
  5. Always blocks: Primitive components with collision

IsPlacedObject

bool IsPlacedObject(AActor* Actor) const;

Checks if an actor is a placed object (implements IPlaceableObjectInterface).

Parameters:

  • Actor - Actor to check

Returns:

  • bool - True if actor is a placed object

Configuration

Settings Methods

SetShowDebugInfo

void SetShowDebugInfo(bool bShow);

Enables/disables debug visualization and logging.

SetPlacementTypeTags

void SetPlacementTypeTags(const TMap<EPlacementType, FName>& InPlacementTypeTags);

Sets the mapping between placement types and surface tags.

Collision System

Oriented Bounding Box Detection

PlacementValidator uses sophisticated collision detection that accounts for object rotation:

// Create oriented collision shape
FCollisionShape CollisionBox = FCollisionShape::MakeBox(CollisionExtent);
 
// Use sweep with rotation for oriented box collision
TArray<FOverlapResult> OverlapResults;
bool bFoundOverlap = CachedWorld->OverlapMultiByChannel(
    OverlapResults,
    CollisionCenter,
    ObjectRotation.Quaternion(), // KEY: Pass rotation for oriented box
    ECollisionChannel::ECC_WorldStatic,
    CollisionBox,
    CollisionParams
);

Collision Margin System

Different placement types use different collision margins:

// Wall placement: Minimal margins
if (CurrentPlacementType == EPlacementType::Wall)
{
    CollisionExtent += FVector(2.0f, 2.0f, 2.0f);
}
else
{
    // Floor/Roof placement: Reasonable Z extent
    CollisionExtent.Z = FMath::Max(CollisionExtent.Z, 25.0f);
    CollisionExtent.X += 1.0f;
    CollisionExtent.Y += 1.0f;
}

Bounds Center Correction

Accurately calculates collision center for objects with offset bounds:

// Calculate bounds offset for accurate collision
FVector LocalBoundsCenter = MeshBounds.Origin;
FRotator ObjectRotation = PreviewActor->GetActorRotation();
FVector WorldSpaceBoundsOffset = ObjectRotation.RotateVector(LocalBoundsCenter);
 
// Apply offset to get correct collision center
CollisionCenter = Location + WorldSpaceBoundsOffset;

Surface Filtering

Wall Surface Handling

For wall placement, automatically ignores nearby wall surfaces:

if (CurrentPlacementType == EPlacementType::Wall)
{
    FName WallSurfaceTag = PlacementTypeTags.FindRef(EPlacementType::Wall);
    if (!WallSurfaceTag.IsNone())
    {
        TArray<AActor*> WallSurfaces;
        UGameplayStatics::GetAllActorsWithTag(CachedWorld, WallSurfaceTag, WallSurfaces);
        
        for (AActor* WallSurface : WallSurfaces)
        {
            float Distance = FVector::Dist(WallSurface->GetActorLocation(), Location);
            if (Distance < 1000.0f) // Within reasonable range
            {
                ActorsToIgnore.Add(WallSurface);
            }
        }
    }
}

Object Type Filtering

Automatically filters different object types:

// Ignore preview and overlay actors
if (PreviewActor) ActorsToIgnore.Add(PreviewActor);
if (OverlayActor) ActorsToIgnore.Add(OverlayActor);
if (EditingTarget) ActorsToIgnore.Add(EditingTarget);
 
// Ignore placement surfaces that aren't collision obstacles
TArray<AActor*> PlacedObjects;
UGameplayStatics::GetAllActorsWithInterface(CachedWorld, UPlaceableObjectInterface::StaticClass(), PlacedObjects);
 
FName TargetSurfaceTag = PlacementTypeTags.FindRef(PlacementType);
for (AActor* PlacedObject : PlacedObjects)
{
    // Only ignore if this placeable object is not a surface for current placement type
    if (!PlacedObject->Tags.Contains(TargetSurfaceTag))
    {
        ActorsToIgnore.Add(PlacedObject);
    }
}

Integration with PlacementSystemComponent

Automatic Configuration

// In PlacementSystemComponent::ConfigureComponents()
if (PlacementValidator)
{
    PlacementValidator->SetShowDebugInfo(bShowDebugInfo);
    PlacementValidator->SetPlacementTypeTags(PlacementTypeTags);
}

Validation Updates

// During placement updates
void UpdatePlacementValidity()
{
    EPlacementValidity NewValidity = PlacementValidator->CheckPlacementValidity(
        CurrentLocation, 
        CurrentRotation, 
        PreviewActor, 
        IsInPrecisionMode(), 
        CurrentPlacementType, 
        OverlayActor, 
        EditingTarget
    );
    
    if (NewValidity != CurrentPlacementValidity)
    {
        CurrentPlacementValidity = NewValidity;
        OnPlacementValidityChanged.Broadcast(CurrentPlacementValidity);
    }
}

Usage Examples

Basic Validation

// Check if location is valid for placement
EPlacementValidity Validity = PlacementValidator->CheckPlacementValidity(
    PlacementLocation,
    PlacementRotation,
    PreviewActor,
    false, // Not in precision mode
    EPlacementType::Floor,
    OverlayActor,
    nullptr // Not editing existing object
);
 
if (Validity == EPlacementValidity::Valid)
{
    // Safe to place object
    PlaceObject(PlacementLocation, PlacementRotation);
}

Custom Validation Logic

// Validate placement with custom checks
bool IsPlacementSafe(AActor* ObjectToPlace, const FVector& Location)
{
    // First check system validation
    EPlacementValidity SystemValidity = PlacementValidator->CheckPlacementValidity(
        Location, FRotator::ZeroRotator, ObjectToPlace, false, EPlacementType::Floor
    );
    
    if (SystemValidity != EPlacementValidity::Valid)
    {
        return false;
    }
    
    // Additional custom checks
    if (IsLocationInRestrictedZone(Location))
    {
        UE_LOG(LogTemp, Warning, TEXT("Location is in restricted zone"));
        return false;
    }
    
    if (!HasRequiredResources(ObjectToPlace))
    {
        UE_LOG(LogTemp, Warning, TEXT("Insufficient resources"));
        return false;
    }
    
    return true;
}

Object Classification

// Check what type of object we're dealing with
void AnalyzeObject(AActor* Actor)
{
    if (PlacementValidator->IsPlacedObject(Actor))
    {
        UE_LOG(LogTemp, Log, TEXT("Object is a placed object"));
        
        // Check if it would block placement
        bool bWouldBlock = PlacementValidator->ShouldActorBlockPlacement(Actor, EPlacementType::Floor);
        UE_LOG(LogTemp, Log, TEXT("Would block floor placement: %s"), bWouldBlock ? TEXT("Yes") : TEXT("No"));
    }
    else
    {
        UE_LOG(LogTemp, Log, TEXT("Object is not a placed object"));
    }
}

Debug Features

Visual Debug Output

When debug info is enabled, PlacementValidator provides:

PlacementValidator->SetShowDebugInfo(true);
 
// Collision detection will now show:
// - Colored debug boxes (Green = valid, Red = collision)
// - Collision center and extent visualization
// - Overlapping actor information
// - Rotation visualization for oriented boxes

Debug Information

if (bShowDebugInfo)
{
    FColor BoxColor = bFoundOverlap ? FColor::Red : FColor::Green;
    
    // Draw rotated debug box
    DrawDebugBox(CachedWorld, CollisionCenter, CollisionExtent, 
                ObjectRotation.Quaternion(), BoxColor, false, 0.1f, 0, 2.0f);
    
    if (bFoundOverlap && OutActors.Num() > 0)
    {
        for (AActor* OverlappingActor : OutActors)
        {
            UE_LOG(LogTemp, Red, TEXT("Overlapping: %s (Rotated Box)"), *OverlappingActor->GetName());
        }
    }
    
    UE_LOG(LogTemp, Cyan, TEXT("Collision Rotation: Pitch=%.1f, Yaw=%.1f, Roll=%.1f"),
           ObjectRotation.Pitch, ObjectRotation.Yaw, ObjectRotation.Roll);
}

Performance Considerations

Optimized Collision Detection

  1. Efficient Bounds Calculation: Uses StaticMesh bounds when available
  2. Reduced Collision Margins: Minimizes false positives
  3. Smart Actor Filtering: Pre-filters objects to reduce collision checks
  4. Oriented Box Optimization: Uses single sweep instead of multiple traces

Best Practices

// Cache validation results when position hasn't changed significantly
class UCachedValidator
{
    FVector LastValidatedLocation = FVector::ZeroVector;
    EPlacementValidity LastResult = EPlacementValidity::Unknown;
    
public:
    EPlacementValidity GetCachedValidity(const FVector& Location)
    {
        // Use cached result if location hasn't changed much
        if (FVector::Dist(Location, LastValidatedLocation) < 5.0f) // 5cm tolerance
        {
            return LastResult;
        }
        
        // Perform new validation
        LastResult = PlacementValidator->CheckPlacementValidity(/* parameters */);
        LastValidatedLocation = Location;
        
        return LastResult;
    }
};

Troubleshooting

Common Issues

"Objects show as invalid when they should be valid"

  • Check collision margins aren't too large
  • Verify bounds center offset calculations
  • Enable debug visualization to see collision boxes
  • Check if objects are being incorrectly filtered

"Rotated objects have incorrect collision"

  • Ensure oriented bounding box collision is working
  • Verify object rotation is passed correctly to collision system
  • Check that collision extent calculation accounts for rotation

"Objects place inside walls/surfaces"

  • Verify placement type tags are set correctly
  • Check surface filtering logic
  • Ensure wall surfaces are being detected properly

"Performance issues during validation"

  • Reduce collision check frequency
  • Implement validation caching
  • Check if debug visualization is disabled in shipping builds

Debug Commands

// Comprehensive validation debugging
void DebugPlacementValidation(const FVector& TestLocation)
{
    PlacementValidator->SetShowDebugInfo(true);
    
    UE_LOG(LogTemp, Log, TEXT("=== Placement Validation Debug ==="));
    UE_LOG(LogTemp, Log, TEXT("Test Location: %s"), *TestLocation.ToString());
    
    if (PreviewActor)
    {
        // Test validation
        EPlacementValidity Result = PlacementValidator->CheckPlacementValidity(
            TestLocation,
            PreviewActor->GetActorRotation(),
            PreviewActor,
            false,
            CurrentPlacementType,
            OverlayActor,
            EditingTarget
        );
        
        UE_LOG(LogTemp, Log, TEXT("Validation Result: %s"), 
               *UEnum::GetValueAsString(Result));
        
        // Test specific object classification
        if (TestActor)
        {
            bool bIsPlaced = PlacementValidator->IsPlacedObject(TestActor);
            bool bWouldBlock = PlacementValidator->ShouldActorBlockPlacement(TestActor, CurrentPlacementType);
            
            UE_LOG(LogTemp, Log, TEXT("Test Actor - Is Placed: %s, Would Block: %s"),
                   bIsPlaced ? TEXT("Yes") : TEXT("No"),
                   bWouldBlock ? TEXT("Yes") : TEXT("No"));
        }
    }
}

PlacementValidator provides robust and accurate placement validation with sophisticated collision detection, intelligent filtering, and comprehensive debugging support to ensure reliable object placement in all scenarios.