Grid Placement System
Library Components
SurfaceManager

SurfaceManager

The SurfaceManager is a specialized library component that handles surface detection, grid visualization, and surface-specific functionality for the Grid Placement System. It automatically discovers tagged surfaces in the level and provides dynamic grid overlays that adapt to different placement types.

Overview

SurfaceManager acts as the bridge between the placement system and the level geometry, automatically discovering surfaces tagged for different placement types (Floor, Wall, Roof) and managing their visual grid overlays. It provides efficient surface grouping, dynamic material management, and real-time grid visualization that appears only when needed.

Key Features

  • Automatic Surface Discovery - Finds all tagged surfaces in the level
  • Dynamic Grid Visualization - Shows grids only for current placement type
  • Material Management - Creates and manages dynamic grid materials
  • Performance Optimized - Efficient surface grouping and rendering
  • Multi-Surface Support - Handles complex levels with many surface types
  • Runtime Refresh - Can detect new surfaces added at runtime
  • Flexible Grid Patterns - Supports custom grid textures and styles

Data Structures

FSurfaceGroup

USTRUCT(BlueprintType)
struct FSurfaceGroup
{
    GENERATED_BODY()
 
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
    TArray<AActor*> Surfaces;
};

Groups all surfaces of the same placement type for efficient management.

Core Methods

Initialization

Initialize

void Initialize(UWorld* World, const TMap<EPlacementType, FName>& PlacementTypeTags);

Initializes the SurfaceManager with world context and tag mappings.

Parameters:

  • World - The world context for surface discovery
  • PlacementTypeTags - Mapping between placement types and surface tags

Surface Management

CollectSurfaces

void CollectSurfaces();

Discovers and catalogs all surfaces in the level based on tags.

Process:

  1. Clears existing surface groups
  2. Searches level for actors with placement type tags
  3. Groups surfaces by placement type
  4. Hides all grids initially

Usage:

// Called during initialization or when level changes
SurfaceManager->CollectSurfaces();

RefreshGrids

void RefreshGrids();

Re-scans the level for surfaces and updates the surface database.

Usage:

// Called when new objects are placed or level geometry changes
SurfaceManager->RefreshGrids();

Grid Visualization

ShowGridForPlacementType

void ShowGridForPlacementType(EPlacementType PlacementType);

Displays grid overlays for all surfaces of the specified placement type.

Parameters:

  • PlacementType - The placement type to show grids for

Process:

  1. Hides all existing grids
  2. Finds surface group for placement type
  3. Creates dynamic grid materials for each surface
  4. Applies grid materials as overlay materials

Material Configuration:

MID->SetScalarParameterValue(TEXT("GridSize"), GridSize);
MID->SetVectorParameterValue(TEXT("GridColor"), GridColor);
MID->SetVectorParameterValue(TEXT("SubGridColor"), SubGridColor);
 
if (GridPattern)
{
    MID->SetTextureParameterValue(TEXT("GridPattern"), GridPattern);
}

HideAllGrids

void HideAllGrids();

Hides all grid overlays across all surface types.

Process:

  • Iterates through all surface groups
  • Removes overlay materials from all surface meshes
  • Validates actors before material operations

Configuration

Grid Settings

SetGridMaterial

void SetGridMaterial(UMaterialInterface* Material);

Sets the base material used for grid overlays.

SetGridSize

void SetGridSize(float Size);

Sets the grid cell size for all grid overlays.

SetGridColor

void SetGridColor(const FLinearColor& Color);

Sets the primary grid line color.

SetSubGridColor

void SetSubGridColor(const FLinearColor& Color);

Sets the sub-division grid line color.

SetGridPattern

void SetGridPattern(UTexture2D* Pattern);

Sets an optional texture pattern for the grid overlay.

Data Access

GetPlacementSurfaces

const TMap<EPlacementType, FSurfaceGroup>& GetPlacementSurfaces() const;

Returns the complete surface database for external access.

Surface Discovery System

Tag-Based Detection

SurfaceManager uses actor tags to identify surfaces:

for (const auto& PlacementTypeTag : PlacementTypeTags)
{
    EPlacementType PlacementType = PlacementTypeTag.Key;
    FName TagName = PlacementTypeTag.Value;
 
    TArray<AActor*> TaggedActors;
    UGameplayStatics::GetAllActorsWithTag(CachedWorld, TagName, TaggedActors);
 
    if (TaggedActors.Num() > 0)
    {
        FSurfaceGroup Group;
        Group.Surfaces = TaggedActors;
        PlacementSurfaces.Add(PlacementType, Group);
    }
}

Surface Validation

Each discovered surface is validated before use:

if (IsValid(Surface) && Surface->GetRootComponent())
{
    if (UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Surface->GetRootComponent()))
    {
        // Surface is valid for grid overlay
        ApplyGridMaterial(Mesh);
    }
}

Grid Material System

Dynamic Material Creation

void ApplyGridToSurface(AActor* Surface, EPlacementType PlacementType)
{
    if (UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Surface->GetRootComponent()))
    {
        UMaterialInstanceDynamic* MID = UMaterialInstanceDynamic::Create(GridMaterial, Surface);
        if (MID)
        {
            // Configure grid parameters
            MID->SetScalarParameterValue(TEXT("GridSize"), GridSize);
            MID->SetVectorParameterValue(TEXT("GridColor"), GridColor);
            MID->SetVectorParameterValue(TEXT("SubGridColor"), SubGridColor);
            
            // Apply optional pattern
            if (GridPattern)
            {
                MID->SetTextureParameterValue(TEXT("GridPattern"), GridPattern);
            }
            
            // Apply as overlay material
            Mesh->SetOverlayMaterial(MID);
        }
    }
}

Material Safety

// Safe material application with validation
if (Mesh && IsValid(Mesh) && !Mesh->IsBeingDestroyed())
{
    Mesh->SetOverlayMaterial(MID);
}
else
{
    UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Mesh became invalid before setting overlay material on %s"), 
           *Surface->GetName());
}

Integration with PlacementSystemComponent

Automatic Configuration

// In PlacementSystemComponent::ConfigureComponents()
if (SurfaceManager)
{
    SurfaceManager->SetGridMaterial(GridMaterial);
    SurfaceManager->SetGridSize(GridSize);
    SurfaceManager->SetGridColor(GridColor);
    SurfaceManager->SetSubGridColor(SubGridColor);
    SurfaceManager->SetGridPattern(GridPattern);
}

Initialization Process

// In PlacementSystemComponent::InitializeComponents()
if (SurfaceManager)
{
    SurfaceManager->Initialize(GetWorld(), PlacementTypeTags);
}
 
// During BeginPlay
if (bAutoCreateGrids && GetWorld() && SurfaceManager)
{
    SurfaceManager->CollectSurfaces();
}

Grid Display Logic

// When placement type changes
if (SurfaceManager)
{
    SurfaceManager->ShowGridForPlacementType(CurrentPlacementType);
}
 
// When objects are placed
if (SurfaceManager)
{
    SurfaceManager->RefreshGrids();
}

Usage Examples

Basic Setup

// Initialize surface manager
TMap<EPlacementType, FName> PlacementTags = {
    {EPlacementType::Floor, TEXT("Floor")},
    {EPlacementType::Wall, TEXT("Wall")},
    {EPlacementType::Roof, TEXT("Roof")}
};
 
SurfaceManager->Initialize(GetWorld(), PlacementTags);
SurfaceManager->SetGridMaterial(GridMaterialAsset);
SurfaceManager->SetGridSize(25.0f);
 
// Discover surfaces in level
SurfaceManager->CollectSurfaces();

Dynamic Grid Control

// Show grids for current placement mode
void UpdateGridDisplay(EPlacementType NewPlacementType)
{
    if (SurfaceManager)
    {
        SurfaceManager->ShowGridForPlacementType(NewPlacementType);
        UE_LOG(LogTemp, Log, TEXT("Showing grids for %s placement"), 
               *UEnum::GetValueAsString(NewPlacementType));
    }
}
 
// Hide all grids (useful for screenshot mode)
void HideAllGrids()
{
    if (SurfaceManager)
    {
        SurfaceManager->HideAllGrids();
        UE_LOG(LogTemp, Log, TEXT("All grids hidden"));
    }
}

Runtime Surface Discovery

// Add new surfaces at runtime
void OnLevelGeometryChanged()
{
    if (SurfaceManager)
    {
        SurfaceManager->RefreshGrids();
        
        // Check how many surfaces were found
        const auto& Surfaces = SurfaceManager->GetPlacementSurfaces();
        
        for (const auto& SurfaceGroup : Surfaces)
        {
            EPlacementType Type = SurfaceGroup.Key;
            int32 Count = SurfaceGroup.Value.Surfaces.Num();
            
            UE_LOG(LogTemp, Log, TEXT("Found %d %s surfaces"), 
                   Count, *UEnum::GetValueAsString(Type));
        }
    }
}

Custom Grid Appearance

// Configure custom grid appearance
void SetupCustomGrids()
{
    SurfaceManager->SetGridSize(50.0f);                    // Larger grid cells
    SurfaceManager->SetGridColor(FLinearColor::Blue);      // Blue main lines
    SurfaceManager->SetSubGridColor(FLinearColor::Cyan);   // Cyan sub-lines
    
    // Optional: Set grid pattern texture
    if (CustomGridTexture)
    {
        SurfaceManager->SetGridPattern(CustomGridTexture);
    }
    
    // Refresh to apply changes
    SurfaceManager->ShowGridForPlacementType(CurrentPlacementType);
}

Surface Analysis

// Analyze discovered surfaces
void AnalyzeLevelSurfaces()
{
    if (!SurfaceManager) return;
    
    const auto& AllSurfaces = SurfaceManager->GetPlacementSurfaces();
    
    UE_LOG(LogTemp, Log, TEXT("=== Level Surface Analysis ==="));
    
    for (const auto& SurfaceGroup : AllSurfaces)
    {
        EPlacementType Type = SurfaceGroup.Key;
        const FSurfaceGroup& Group = SurfaceGroup.Value;
        
        UE_LOG(LogTemp, Log, TEXT("%s Surfaces: %d"), 
               *UEnum::GetValueAsString(Type), Group.Surfaces.Num());
        
        // Analyze individual surfaces
        for (AActor* Surface : Group.Surfaces)
        {
            if (Surface)
            {
                FVector Location = Surface->GetActorLocation();
                FVector Bounds = Surface->GetActorBounds(false).BoxExtent;
                
                UE_LOG(LogTemp, Log, TEXT("  - %s at %s (Size: %.1fx%.1fx%.1f)"), 
                       *Surface->GetName(), *Location.ToString(),
                       Bounds.X * 2, Bounds.Y * 2, Bounds.Z * 2);
            }
        }
    }
}

Performance Considerations

Efficient Surface Management

  1. Batched Operations: Groups surfaces by type for efficient processing
  2. Lazy Material Creation: Creates materials only when grids are shown
  3. Validation Checks: Prevents crashes with invalid or destroyed actors
  4. Memory Management: Properly manages dynamic material instances

Best Practices

// Efficient surface discovery
void OptimizedSurfaceCollection()
{
    // Cache world reference to avoid repeated lookups
    if (!CachedWorld) return;
    
    // Use batch operations when possible
    TArray<AActor*> AllActors;
    UGameplayStatics::GetAllActorsOfClass(CachedWorld, AActor::StaticClass(), AllActors);
    
    // Pre-filter by tags in single pass
    TMap<EPlacementType, TArray<AActor*>> FilteredSurfaces;
    
    for (AActor* Actor : AllActors)
    {
        if (!Actor) continue;
        
        for (const auto& TagPair : PlacementTypeTags)
        {
            if (Actor->Tags.Contains(TagPair.Value))
            {
                FilteredSurfaces.FindOrAdd(TagPair.Key).Add(Actor);
                break; // Actor can only belong to one surface type
            }
        }
    }
    
    // Update surface groups
    PlacementSurfaces.Empty();
    for (const auto& FilteredGroup : FilteredSurfaces)
    {
        FSurfaceGroup Group;
        Group.Surfaces = FilteredGroup.Value;
        PlacementSurfaces.Add(FilteredGroup.Key, Group);
    }
}

Memory Optimization

// Prevent memory leaks with dynamic materials
void CleanupGridMaterials()
{
    for (const auto& SurfaceGroup : PlacementSurfaces)
    {
        for (AActor* Surface : SurfaceGroup.Value.Surfaces)
        {
            if (IsValid(Surface))
            {
                if (UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Surface->GetRootComponent()))
                {
                    // Clear overlay material to free dynamic material instance
                    Mesh->SetOverlayMaterial(nullptr);
                }
            }
        }
    }
}

Error Handling

Surface Validation

bool ValidateSurface(AActor* Surface)
{
    if (!Surface)
    {
        UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Null surface actor"));
        return false;
    }
    
    if (!IsValid(Surface))
    {
        UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Invalid surface actor"));
        return false;
    }
    
    if (Surface->IsBeingDestroyed())
    {
        UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Surface is being destroyed"));
        return false;
    }
    
    if (!Surface->GetRootComponent())
    {
        UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Surface has no root component"));
        return false;
    }
    
    UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Surface->GetRootComponent());
    if (!Mesh)
    {
        UE_LOG(LogTemp, Warning, TEXT("SurfaceManager: Surface root is not StaticMeshComponent"));
        return false;
    }
    
    return true;
}

Safe Material Application

void SafeApplyGridMaterial(AActor* Surface, UMaterialInstanceDynamic* Material)
{
    if (!ValidateSurface(Surface)) return;
    
    UStaticMeshComponent* Mesh = Cast<UStaticMeshComponent>(Surface->GetRootComponent());
    
    try
    {
        Mesh->SetOverlayMaterial(Material);
    }
    catch (...)
    {
        UE_LOG(LogTemp, Error, TEXT("SurfaceManager: Failed to apply grid material to %s"), 
               *Surface->GetName());
    }
}

Troubleshooting

Common Issues

"No grids showing"

  • Verify surfaces are tagged correctly in the level
  • Check that GridMaterial is assigned and valid
  • Ensure CollectSurfaces() has been called
  • Verify ShowGridForPlacementType() is being called

"Grids appear on wrong surfaces"

  • Check PlacementTypeTags mapping
  • Verify actor tags match exactly (case-sensitive)
  • Ensure surfaces don't have multiple placement tags

"Performance issues with many surfaces"

  • Consider using LODs for complex surface meshes
  • Implement distance-based grid culling
  • Batch surface operations where possible

"Grids not updating when level changes"

  • Call RefreshGrids() after placing objects
  • Ensure new surfaces have proper tags
  • Check that surface actors are properly registered in world

Debug Commands

// Debug surface discovery
void DebugSurfaceManager()
{
    if (!SurfaceManager) return;
    
    UE_LOG(LogTemp, Log, TEXT("=== SurfaceManager Debug ==="));
    
    const auto& AllSurfaces = SurfaceManager->GetPlacementSurfaces();
    UE_LOG(LogTemp, Log, TEXT("Surface Groups: %d"), AllSurfaces.Num());
    
    for (const auto& SurfaceGroup : AllSurfaces)
    {
        EPlacementType Type = SurfaceGroup.Key;
        const FSurfaceGroup& Group = SurfaceGroup.Value;
        
        UE_LOG(LogTemp, Log, TEXT("%s: %d surfaces"), 
               *UEnum::GetValueAsString(Type), Group.Surfaces.Num());
        
        // Validate each surface
        int32 ValidSurfaces = 0;
        for (AActor* Surface : Group.Surfaces)
        {
            if (ValidateSurface(Surface))
            {
                ValidSurfaces++;
            }
        }
        
        UE_LOG(LogTemp, Log, TEXT("  Valid: %d, Invalid: %d"), 
               ValidSurfaces, Group.Surfaces.Num() - ValidSurfaces);
    }
}
 
// Test grid display
void TestGridDisplay()
{
    UE_LOG(LogTemp, Log, TEXT("Testing grid display..."));
    
    // Test each placement type
    TArray<EPlacementType> PlacementTypes = {
        EPlacementType::Floor,
        EPlacementType::Wall,
        EPlacementType::Roof
    };
    
    for (EPlacementType Type : PlacementTypes)
    {
        UE_LOG(LogTemp, Log, TEXT("Showing %s grids"), *UEnum::GetValueAsString(Type));
        SurfaceManager->ShowGridForPlacementType(Type);
        
        // Wait briefly between tests
        FPlatformProcess::Sleep(1.0f);
    }
    
    // Hide all at the end
    SurfaceManager->HideAllGrids();
    UE_LOG(LogTemp, Log, TEXT("Grid display test complete"));
}

SurfaceManager provides robust surface discovery and grid visualization that automatically adapts to level geometry while maintaining excellent performance and providing comprehensive debugging capabilities.