Grid Placement System
Library Components
GridSnapper

GridSnapper

The GridSnapper is a specialized library component that handles all grid-based snapping calculations and object positioning logic. It provides precise grid alignment, bounds calculation, and placement offset computations for the Grid Placement System.

Overview

GridSnapper is responsible for converting world positions into grid-aligned positions, calculating object footprints, and determining proper placement offsets. It works behind the scenes to ensure objects snap perfectly to the grid system while accounting for rotation and object bounds.

Key Features

  • Precise Grid Snapping - Converts any world position to grid-aligned coordinates
  • Rotation-Aware Bounds - Calculates accurate bounds for rotated objects
  • Footprint Analysis - Determines how many grid cells an object occupies
  • Placement Offsets - Calculates proper offsets for wall placement
  • Debug Visualization - Optional debug output for troubleshooting
  • Performance Optimized - Efficient calculations for real-time snapping

Core Methods

Grid Snapping

SnapToGrid

FVector SnapToGrid(const FVector& WorldPosition, AActor* SurfaceActor) const;

Snaps a world position to the nearest grid intersection point.

Parameters:

  • WorldPosition - The world position to snap
  • SurfaceActor - The surface actor for context (can be null)

Returns:

  • FVector - Grid-aligned world position

Usage:

FVector PlayerCursorPos = GetMouseWorldPosition();
FVector SnappedPos = GridSnapper->SnapToGrid(PlayerCursorPos, FloorActor);

SnapToGridWithBounds

FVector SnapToGridWithBounds(const FVector& WorldPosition, AActor* SurfaceActor, const FVector2D& ObjectSize, float ObjectRotationYaw) const;

Snaps a position while accounting for object bounds and rotation.

Parameters:

  • WorldPosition - The world position to snap
  • SurfaceActor - The surface actor for context
  • ObjectSize - 2D size of the object footprint
  • ObjectRotationYaw - Object's Y-axis rotation in degrees

Returns:

  • FVector - Grid-aligned position accounting for object bounds

Bounds Calculation

GetActorBounds2D

FVector2D GetActorBounds2D(AActor* Actor) const;

Calculates the 2D footprint size of an actor.

Parameters:

  • Actor - The actor to measure

Returns:

  • FVector2D - Width (X) and Height (Y) of the object's footprint

CalculateRotatedBounds

FVector2D CalculateRotatedBounds(const FVector2D& ObjectSize, float ObjectRotationYaw) const;

Calculates how object bounds change when rotated.

Parameters:

  • ObjectSize - Original object size (width, height)
  • ObjectRotationYaw - Rotation angle in degrees

Returns:

  • FVector2D - New bounds after rotation

Example:

// A 2x1 object rotated 45° becomes roughly 2.12x2.12
FVector2D OriginalSize(200.0f, 100.0f);
FVector2D RotatedSize = GridSnapper->CalculateRotatedBounds(OriginalSize, 45.0f);
// Result: approximately (212.0f, 212.0f)

Grid Occupancy

CalculateGridOccupancy

void CalculateGridOccupancy(const FVector2D& RotatedSize, int32& OutGridCellsX, int32& OutGridCellsY) const;

Determines how many grid cells an object occupies.

Parameters:

  • RotatedSize - Object size after rotation
  • OutGridCellsX - [Output] Number of grid cells in X direction
  • OutGridCellsY - [Output] Number of grid cells in Y direction

Example:

FVector2D ObjectSize(150.0f, 75.0f); // 1.5x0.75 meters
int32 CellsX, CellsY;
GridSnapper->CalculateGridOccupancy(ObjectSize, CellsX, CellsY);
// With 25cm grid: CellsX = 6, CellsY = 3

Placement Calculations

CalculatePlacementOffset

FVector CalculatePlacementOffset(AActor* Object, const FVector& Normal) const;

Calculates offset needed to place object properly on a surface (especially walls).

Parameters:

  • Object - The object being placed
  • Normal - Surface normal vector

Returns:

  • FVector - Offset to apply to placement position

CalculatePivotToFootprintCenterOffset

FVector CalculatePivotToFootprintCenterOffset(AActor* Actor) const;

Calculates offset from object pivot to its footprint center.

Parameters:

  • Actor - The actor to analyze

Returns:

  • FVector - Offset from pivot to footprint center (Z component is always 0)

Configuration

Settings

SetGridSize

void SetGridSize(float Size);

Sets the grid cell size in Unreal units.

Parameters:

  • Size - Grid cell size (default: 25.0 = 25cm)

SetShowDebugInfo

void SetShowDebugInfo(bool bShow);

Enables/disables debug output and logging.

Parameters:

  • bShow - Whether to show debug information

GetGridSize

float GetGridSize() const;

Returns the current grid size setting.

Constants and Thresholds

MIN_ROTATION_THRESHOLD

static constexpr float MIN_ROTATION_THRESHOLD = 1.0f;

Minimum rotation angle (in degrees) before bounds recalculation is performed. Rotations smaller than this are ignored for performance.

Usage Examples

Basic Grid Snapping

// Simple position snapping
FVector MousePos = GetMouseWorldPosition();
FVector SnappedPos = GridSnapper->SnapToGrid(MousePos, nullptr);
PreviewActor->SetActorLocation(SnappedPos);

Object Placement with Bounds

// Snap considering object size and rotation
FVector2D ObjectSize = GridSnapper->GetActorBounds2D(PreviewActor);
float CurrentRotation = PreviewActor->GetActorRotation().Yaw;
 
FVector SnappedPos = GridSnapper->SnapToGridWithBounds(
    MouseWorldPos, 
    SurfaceActor, 
    ObjectSize, 
    CurrentRotation
);
 
PreviewActor->SetActorLocation(SnappedPos);

Grid Occupancy Analysis

// Calculate how much space an object needs
FVector2D ObjectSize = GridSnapper->GetActorBounds2D(FurnitureActor);
FVector2D RotatedSize = GridSnapper->CalculateRotatedBounds(ObjectSize, 45.0f);
 
int32 GridCellsX, GridCellsY;
GridSnapper->CalculateGridOccupancy(RotatedSize, GridCellsX, GridCellsY);
 
UE_LOG(LogTemp, Log, TEXT("Object occupies %dx%d grid cells"), GridCellsX, GridCellsY);

Wall Placement

// Calculate proper wall mounting offset
FVector PlacementOffset = GridSnapper->CalculatePlacementOffset(WallMountedObject, WallNormal);
FVector FinalPosition = SnappedPosition + PlacementOffset;
 
WallMountedObject->SetActorLocation(FinalPosition);

Debug Features

When debug info is enabled, GridSnapper provides detailed logging:

GridSnapper->SetShowDebugInfo(true);
 
// This will now log detailed information:
FVector SnappedPos = GridSnapper->SnapToGrid(WorldPos, SurfaceActor);
// Output: "GridSnap: GridSize=25.0, Step=(12.5,7.3) World(112.7,157.3)->Snap(125.0,162.5)"

Debug information includes:

  • Grid size settings
  • Step distances for X and Y axes
  • Original and snapped positions
  • Grid occupancy calculations
  • Bounds calculations for rotated objects

Performance Considerations

Optimization Features

  1. Rotation Threshold: Small rotations (< 1°) skip expensive bounds recalculation
  2. Efficient Snapping: Uses half-grid-size snapping for responsive feedback
  3. Cached Calculations: Bounds calculations are optimized for repeated calls
  4. Minimal Allocations: All calculations use stack-allocated variables

Best Practices

// Cache object size if placing multiple instances
FVector2D CachedObjectSize = GridSnapper->GetActorBounds2D(PrototypeActor);
 
for (int32 i = 0; i < PlacementCount; ++i)
{
    // Reuse cached size instead of recalculating
    FVector SnappedPos = GridSnapper->SnapToGridWithBounds(
        PlacementPositions[i], 
        SurfaceActor, 
        CachedObjectSize,  // Use cached value
        RotationAngles[i]
    );
}

Integration with PlacementSystemComponent

GridSnapper is automatically configured by PlacementSystemComponent:

// Automatic configuration in PlacementSystemComponent
if (GridSnapper)
{
    GridSnapper->SetGridSize(GridSize);                    // From component settings
    GridSnapper->SetShowDebugInfo(bShowDebugInfo);         // From debug settings
}

The component uses GridSnapper throughout the placement process:

  1. Preview Updates: Snaps preview objects to grid in real-time
  2. Placement Validation: Ensures objects align properly before placement
  3. Overlay Sizing: Calculates correct overlay dimensions based on object bounds
  4. Collision Detection: Uses accurate bounds for placement validation

Troubleshooting

Common Issues

"Objects not snapping to grid"

  • Verify GridSize is set correctly (> 0)
  • Check that SnapToGrid is being called during preview updates
  • Enable debug info to see snapping calculations

"Rotated objects have wrong bounds"

  • Ensure CalculateRotatedBounds is called when rotation changes
  • Check rotation values are in degrees, not radians
  • Verify MIN_ROTATION_THRESHOLD isn't preventing updates

"Grid occupancy incorrect"

  • Verify object has valid StaticMeshComponent
  • Check that GetActorBounds2D returns reasonable values
  • Ensure grid size matches level design expectations

Debug Commands

// Test grid snapping behavior
void TestGridSnapping()
{
    GridSnapper->SetShowDebugInfo(true);
    
    FVector TestPosition(127.3f, 183.7f, 0.0f);
    FVector SnappedPosition = GridSnapper->SnapToGrid(TestPosition, nullptr);
    
    UE_LOG(LogTemp, Log, TEXT("Original: %s"), *TestPosition.ToString());
    UE_LOG(LogTemp, Log, TEXT("Snapped: %s"), *SnappedPosition.ToString());
    
    // Test object bounds
    if (TestActor)
    {
        FVector2D Bounds = GridSnapper->GetActorBounds2D(TestActor);
        UE_LOG(LogTemp, Log, TEXT("Object bounds: %.1fx%.1f"), Bounds.X, Bounds.Y);
        
        int32 CellsX, CellsY;
        GridSnapper->CalculateGridOccupancy(Bounds, CellsX, CellsY);
        UE_LOG(LogTemp, Log, TEXT("Grid occupancy: %dx%d cells"), CellsX, CellsY);
    }
}

GridSnapper provides the mathematical foundation for precise grid-based placement, ensuring objects align perfectly with the grid system while maintaining excellent performance and flexibility.