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 snapSurfaceActor
- 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 snapSurfaceActor
- The surface actor for contextObjectSize
- 2D size of the object footprintObjectRotationYaw
- 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 rotationOutGridCellsX
- [Output] Number of grid cells in X directionOutGridCellsY
- [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 placedNormal
- 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
- Rotation Threshold: Small rotations (< 1°) skip expensive bounds recalculation
- Efficient Snapping: Uses half-grid-size snapping for responsive feedback
- Cached Calculations: Bounds calculations are optimized for repeated calls
- 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:
- Preview Updates: Snaps preview objects to grid in real-time
- Placement Validation: Ensures objects align properly before placement
- Overlay Sizing: Calculates correct overlay dimensions based on object bounds
- 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.