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 validateRotation
- Object rotation at placement locationPreviewActor
- The preview actor being placedbIsInPrecisionMode
- Whether precision mode is activeCurrentPlacementType
- Type of placement (Floor/Wall/Roof)OverlayActor
- Overlay actor to ignore during collisionEditingTarget
- Object being edited (ignored during collision)
Returns:
EPlacementValidity::Valid
- Safe to place objectEPlacementValidity::Invalid
- Collision or invalid placementEPlacementValidity::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:
- Always blocks: Pawns (players, NPCs)
- Always blocks: Objects implementing IPlaceableObjectInterface
- Context-sensitive: Placement surfaces
- Matching surfaces (Floor on Floor): Allow
- Different surfaces (Floor on Wall): Block
- Always blocks: Static meshes with collision enabled
- 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
- Efficient Bounds Calculation: Uses StaticMesh bounds when available
- Reduced Collision Margins: Minimizes false positives
- Smart Actor Filtering: Pre-filters objects to reduce collision checks
- 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.