Create a custom UBrushComponent?

I would like to create a custom UBrushComponent for ProBuilder (I need wireframe rendering).

I created a custom UPrimitiveComponent doing the same thing as UBrushComponent.

The problem is that I can’t see the component (even though I add geometry). Why?

Here is the custom UPrimitiveComponent:

PBMC.h

UCLASS(Blueprintable)
class UPBMC : public UPrimitiveComponent
{
    GENERATED_UCLASS_BODY()
    
    UPROPERTY()
    class UModel* Model;
    virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override;
public:
    virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
    virtual void GetUsedMaterials( TArray<UMaterialInterface*>& OutMaterials ) const override;
    
    UFUNCTION(BlueprintCallable, meta = (DisplayName = "AddFaceToModel"), Category = "Javascript")
    void AddFaceToModel(const TArray<FVector>& Vertices); // Used to add geometry to the component
    
    UFUNCTION(BlueprintCallable, meta = (DisplayName = "UpdateVertices"), Category = "Javascript")
    void UpdateVertices();
};

PBMC.cpp

UPBMC::UPBMC(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    Model = NULL;
}

FBoxSphereBounds UPBMC::CalcBounds(const FTransform& LocalToWorld) const { /* computes bounds */ }
void UPBMC::GetUsedMaterials( TArray<UMaterialInterface*>& OutMaterials ) const { /* get materials from polys */ }

FPrimitiveSceneProxy* UPBMC::CreateSceneProxy()
{
    FPrimitiveSceneProxy* Proxy = NULL;

    // FMeshSceneProxy is just a copy of `FBrushSceneProxy`

    if (Model != NULL)
    {
        // Check to make sure that we want to draw this brushed based on editor settings.
        AActor*	Owner = Cast<AActor>(GetOwner());
        if(Owner)
        {
            // If the editor is in a state where drawing the brush wireframe isn't desired, bail out.
            if( GEngine->ShouldDrawBrushWireframe( Owner ) )
            {
                Proxy = new FMeshSceneProxy(this, Model, Owner);
            }
        }
        else
        {
            Proxy = new FMeshSceneProxy(this, Model, Owner);
        }
    }
    
    return Proxy;
}

 // Used to add geometry to the component
void UPBMC::AddFaceToModel(const TArray<FVector>& Vertices) {
    
    if (Model == NULL)
    {
        Model = NewObject<UModel>(GetWorld(), TEXT("PBModel"));
        Model->Initialize();
        Model->Polys = NewObject<UPolys>(GetOuter(), NAME_None, RF_Transactional);
    }
    
    FPoly NewPoly;
    NewPoly.Init();
    NewPoly.Base = GetOwner()->GetActorLocation();
    
    for(const FVector& V: Vertices) {
        UE_LOG(LogTemp, Warning, TEXT("V: %s"), *V.ToString());
        NewPoly.Vertices.Add(V);
    }
    
    if( NewPoly.Finalize( NULL, 1 ) == 0 )
    {
        Model->Polys->Element.Add( NewPoly );
    }
}

void UPBMC::UpdateVertices()
{
    if (Model != NULL)
    {
        Model->BuildVertexBuffers();
        Model->UpdateVertices();
    }
}

FMeshSceneProxy is just a copy of FBrushSceneProxy.

Here is the full code of the custom scene proxy (copied from BrushComponent.cpp):

struct FPBModelWireVertex
{
    FVector Position;
    FPackedNormal TangentX;
    FPackedNormal TangentZ;
    FVector2D UV;
};


class FPBModelWireVertexBuffer : public FVertexBuffer
{
public:
    
    /** Initialization constructor. */
    FPBModelWireVertexBuffer(UModel* InModel):
    NumVertices(0)
    {
        
        Polys.Append(InModel->Polys->Element);
        for(int32 PolyIndex = 0;PolyIndex < InModel->Polys->Element.Num();PolyIndex++)
        {
            NumVertices += InModel->Polys->Element[PolyIndex].Vertices.Num();
        }

    }
    
    // FRenderResource interface.
    virtual void InitRHI() override
    {
        if(NumVertices)
        {
            FRHIResourceCreateInfo CreateInfo;
            VertexBufferRHI = RHICreateVertexBuffer(NumVertices * sizeof(FPBModelWireVertex),BUF_Static, CreateInfo);
            
            FPBModelWireVertex* DestVertex = (FPBModelWireVertex*)RHILockVertexBuffer(VertexBufferRHI,0,NumVertices * sizeof(FPBModelWireVertex),RLM_WriteOnly);
            for(int32 PolyIndex = 0;PolyIndex < Polys.Num();PolyIndex++)
            {
                FPoly& Poly = Polys[PolyIndex];
                for(int32 VertexIndex = 0;VertexIndex < Poly.Vertices.Num();VertexIndex++)
                {
                    DestVertex->Position = Poly.Vertices[VertexIndex];
                    DestVertex->TangentX = FVector(1,0,0);
                    DestVertex->TangentZ = FVector(0,0,1);
                    // TangentZ.w contains the sign of the tangent basis determinant. Assume +1
                    DestVertex->TangentZ.Vector.W = 255;
                    DestVertex->UV.X	 = 0.0f;
                    DestVertex->UV.Y	 = 0.0f;
                    DestVertex++;
                }
            }
            RHIUnlockVertexBuffer(VertexBufferRHI);
        }
    }
    
    // Accessors.
    uint32 GetNumVertices() const { return NumVertices; }
    
private:
    TArray<FPoly> Polys;
    uint32 NumVertices;
};

class FPBModelWireIndexBuffer : public FIndexBuffer
{
public:
    
    /** Initialization constructor. */
    FPBModelWireIndexBuffer(UModel* InModel):
    NumEdges(0)
    {
        
        Polys.Append(InModel->Polys->Element);
        for(int32 PolyIndex = 0;PolyIndex < InModel->Polys->Element.Num();PolyIndex++)
        {
            NumEdges += InModel->Polys->Element[PolyIndex].Vertices.Num();
        }

    }
    
    // FRenderResource interface.
    virtual void InitRHI() override
    {
        if(NumEdges)
        {
            FRHIResourceCreateInfo CreateInfo;
            IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16),NumEdges * 2 * sizeof(uint16),BUF_Static, CreateInfo);
            
            uint16* DestIndex = (uint16*)RHILockIndexBuffer(IndexBufferRHI,0,NumEdges * 2 * sizeof(uint16),RLM_WriteOnly);
            uint16 BaseIndex = 0;
            for(int32 PolyIndex = 0;PolyIndex < Polys.Num();PolyIndex++)
            {
                FPoly&	Poly = Polys[PolyIndex];
                for(int32 VertexIndex = 0;VertexIndex < Poly.Vertices.Num();VertexIndex++)
                {
                    *DestIndex++ = BaseIndex + VertexIndex;
                    *DestIndex++ = BaseIndex + ((VertexIndex + 1) % Poly.Vertices.Num());
                }
                BaseIndex += Poly.Vertices.Num();
            }
            RHIUnlockIndexBuffer(IndexBufferRHI);
        }
    }
    
    // Accessors.
    uint32 GetNumEdges() const { return NumEdges; }
    
private:
    TArray<FPoly> Polys;
    uint32 NumEdges;
};


class FMeshSceneProxy : public FPrimitiveSceneProxy
{
    
public:
    FMeshSceneProxy(UPrimitiveComponent* Component, UModel* InModel, AActor* Owner):
    
    FPrimitiveSceneProxy(Component),
    WireIndexBuffer(InModel),
    WireVertexBuffer(InModel),
    bVolume(false),
    bBuilder(false),
    bSolidWhenSelected(false),
    bInManipulation(false),
    BrushColor(GEngine->C_BrushWire),
    BodySetup(nullptr),
    CollisionResponse(Component->GetCollisionResponseToChannels())
    {
        
//        if(BodySetup == NULL && Owner)
//        {
//            BodySetup = NewObject<UBodySetup>(Owner);
//            check(BodySetup);
//            BodySetup->CollisionTraceFlag = CTF_UseSimpleAsComplex;
//            BodySetup->CreateFromModel( InModel, true );
//        }
        
        bWillEverBeLit = false;
        
        if(Owner)
        {
            // If the editor is in a state where drawing the brush wireframe isn't desired, bail out.
//            if( !GEngine->ShouldDrawBrushWireframe( Owner ) )
//            {
//                return;
//            }
            
            // Determine the type of brush this is.
            bVolume = false; // Owner->IsVolumeBrush();
            bBuilder = true; // FActorEditorUtils::IsABuilderBrush( Owner );
            BrushColor = GEngine->C_BrushWire; // Owner->GetWireColor();
            bSolidWhenSelected = true; // Owner->bSolidWhenSelected;
            bInManipulation = true; // Owner->bInManipulation;
            
            // Builder brushes should be unaffected by level coloration, so if this is a builder brush, use
            // the brush color as the level color.
            if ( bBuilder )
            {
                LevelColor = BrushColor;
            }
            else
            {
                // Try to find a color for level coloration.
                ULevel* Level = Owner->GetLevel();
                ULevelStreaming* LevelStreaming = FLevelUtils::FindStreamingLevel( Level );
                if ( LevelStreaming )
                {
                    LevelColor = LevelStreaming->LevelColor;
                }
            }
        }
        
        bUseEditorDepthTest = !bInManipulation;
        
        // Get a color for property coloration.
        FColor NewPropertyColor;
        GEngine->GetPropertyColorationColor( (UObject*)Component, NewPropertyColor );
        PropertyColor = NewPropertyColor;
        

        ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
                                                   InitBrushVertexFactory,
                                                   FLocalVertexFactory*,VertexFactory,&VertexFactory,
                                                   FVertexBuffer*,WireVertexBuffer,&WireVertexBuffer,
                                                   {
                                                       FLocalVertexFactory::FDataType Data;
                                                       Data.PositionComponent = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(WireVertexBuffer,FPBModelWireVertex,Position,VET_Float3);
                                                       Data.TangentBasisComponents[0] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(WireVertexBuffer,FPBModelWireVertex,TangentX,VET_PackedNormal);
                                                       Data.TangentBasisComponents[1] = STRUCTMEMBER_VERTEXSTREAMCOMPONENT(WireVertexBuffer,FPBModelWireVertex,TangentZ,VET_PackedNormal);
                                                       Data.TextureCoordinates.Add( STRUCTMEMBER_VERTEXSTREAMCOMPONENT(WireVertexBuffer,FPBModelWireVertex,UV,VET_Float2) );
                                                       VertexFactory->SetData(Data);
                                                   });

    }
    
    virtual ~FMeshSceneProxy()
    {

        VertexFactory.ReleaseResource();
        WireIndexBuffer.ReleaseResource();
        WireVertexBuffer.ReleaseResource();

        
    }
    
    bool IsCollisionView(const FEngineShowFlags& EngineShowFlags, bool & bDrawCollision) const
    {
        const bool bInCollisionView = EngineShowFlags.CollisionVisibility || EngineShowFlags.CollisionPawn;
        if (bInCollisionView && IsCollisionEnabled())
        {
            bDrawCollision = EngineShowFlags.CollisionPawn && (CollisionResponse.GetResponse(ECC_Pawn) != ECR_Ignore);
            bDrawCollision |= EngineShowFlags.CollisionVisibility && (CollisionResponse.GetResponse(ECC_Visibility) != ECR_Ignore);
        }
        else
        {
            bDrawCollision = false;
        }
        
        return bInCollisionView;
    }
    
    virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
    {
        QUICK_SCOPE_CYCLE_COUNTER( STAT_BrushSceneProxy_GetDynamicMeshElements );
        
        if( AllowDebugViewmodes() )
        {
            for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
            {
                if (VisibilityMap & (1 << ViewIndex))
                {
                    const FSceneView* View = Views[ViewIndex];
                    
                    bool bDrawCollision = false;
                    const bool bInCollisionView = IsCollisionView(ViewFamily.EngineShowFlags, bDrawCollision);
                    
                    // Draw solid if 'solid when selected' and selected, or we are in a 'collision view'
                    const bool bDrawSolid = ((bSolidWhenSelected && IsSelected()) || (bInCollisionView && bDrawCollision));
                    // Don't draw wireframe if in a collision view mode and not drawing solid
                    const bool bDrawWireframe = !bInCollisionView;
                    
                    // Choose color to draw it
                    FLinearColor DrawColor = BrushColor;
                    // In a collision view mode
                    if(bInCollisionView)
                    {
                        DrawColor = BrushColor;
                    }
                    else if(View->Family->EngineShowFlags.PropertyColoration)
                    {
                        DrawColor = PropertyColor;
                    }
                    else if(View->Family->EngineShowFlags.LevelColoration)
                    {
                        DrawColor = LevelColor;
                    }
                    
                    
                    // SOLID
                    if(bDrawSolid)
                    {
                        if(BodySetup != NULL)
                        {
                            auto SolidMaterialInstance = new FColoredMaterialRenderProxy(
                                                                                         GEngine->ShadedLevelColorationUnlitMaterial->GetRenderProxy(IsSelected(), IsHovered()),
                                                                                         DrawColor
                                                                                         );
                            
                            Collector.RegisterOneFrameMaterialProxy(SolidMaterialInstance);
                            
                            FTransform GeomTransform(GetLocalToWorld());
                            BodySetup->AggGeom.GetAggGeom(GeomTransform, DrawColor.ToFColor(true), /*Material=*/SolidMaterialInstance, false, /*bSolid=*/ true, UseEditorDepthTest(), ViewIndex, Collector);
                        }
                    }
                    // WIREFRAME
                    else if(bDrawWireframe)
                    {
                        // If we have the editor data (Wire Buffers) use those for wireframe

                        if(WireIndexBuffer.GetNumEdges() && WireVertexBuffer.GetNumVertices())
                        {
                            auto WireframeMaterial = new FColoredMaterialRenderProxy(
                                                                                     GEngine->LevelColorationUnlitMaterial->GetRenderProxy(IsSelected(), IsHovered()),
                                                                                     GetViewSelectionColor(DrawColor, *View, !(GIsEditor && (View->Family->EngineShowFlags.Selection)) || IsSelected(), IsHovered(), false, IsIndividuallySelected() )
                                                                                     );
                            
                            Collector.RegisterOneFrameMaterialProxy(WireframeMaterial);
                            
                            FMeshBatch& Mesh = Collector.AllocateMesh();
                            FMeshBatchElement& BatchElement = Mesh.Elements[0];
                            BatchElement.IndexBuffer = &WireIndexBuffer;
                            Mesh.VertexFactory = &VertexFactory;
                            Mesh.MaterialRenderProxy = WireframeMaterial;
                            BatchElement.PrimitiveUniformBufferResource = &GetUniformBuffer();
                            BatchElement.FirstIndex = 0;
                            BatchElement.NumPrimitives = WireIndexBuffer.GetNumEdges();
                            BatchElement.MinVertexIndex = 0;
                            BatchElement.MaxVertexIndex = WireVertexBuffer.GetNumVertices() - 1;
                            Mesh.CastShadow = false;
                            Mesh.Type = PT_LineList;
                            Mesh.DepthPriorityGroup = IsSelected() ? SDPG_Foreground : SDPG_World;
                            Collector.AddMesh(ViewIndex, Mesh);
                        }
                        else

                            if(BodySetup != NULL)
                                // If not, use the body setup for wireframe
                            {
                                FTransform GeomTransform(GetLocalToWorld());
                                BodySetup->AggGeom.GetAggGeom(GeomTransform, GetSelectionColor(DrawColor, IsSelected(), IsHovered()).ToFColor(true), /* Material=*/ NULL, false, /* bSolid=*/ false, UseEditorDepthTest(), ViewIndex, Collector);
                            }
                        
                    }
                }
            }
        }
    }
    
    virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override
    {
        bool bVisible = false;
        
        
        // We render volumes in collision view. In game, always, in editor, if the EngineShowFlags.Volumes option is on.
        if(bSolidWhenSelected && IsSelected())
        {
            FPrimitiveViewRelevance Result;
            Result.bDrawRelevance = true;
            Result.bDynamicRelevance = true;
            return Result;
        }
        
        const bool bInCollisionView = (View->Family->EngineShowFlags.Collision || View->Family->EngineShowFlags.CollisionVisibility || View->Family->EngineShowFlags.CollisionPawn);
        
        if(IsShown(View))
        {
            bool bNeverShow = false;
            
            if( GIsEditor )
            {
                const bool bShowBuilderBrush = View->Family->EngineShowFlags.BuilderBrush != 0;
                
                // Only render builder brush and only if the show flags indicate that we should render builder brushes.
                if( bBuilder && (!bShowBuilderBrush) )
                {
                    bNeverShow = true;
                }
            }
            
            if(bNeverShow == false)
            {
                const bool bBSPVisible = View->Family->EngineShowFlags.BSP;
                const bool bBrushesVisible = View->Family->EngineShowFlags.Brushes;
                
                if ( !bVolume ) // EngineShowFlags.Collision does not apply to volumes
                {
                    if( (bBSPVisible && bBrushesVisible) )
                    {
                        bVisible = true;
                    }
                }
                
                // See if we should be visible because we are in a 'collision view' and have collision enabled
                if (bInCollisionView && IsCollisionEnabled())
                {
                    bVisible = true;
                }
                
                // Always show the build brush and any brushes that are selected in the editor.
                if( GIsEditor )
                {
                    if( bBuilder || IsSelected() )
                    {
                        bVisible = true;
                    }
                }
                
                if ( bVolume )
                {
                    const bool bVolumesVisible = View->Family->EngineShowFlags.Volumes;
                    if(!GIsEditor || View->bIsGameView || bVolumesVisible)
                    {
                        bVisible = true;
                    }
                }		
            }
        }
        
        FPrimitiveViewRelevance Result;
        Result.bDrawRelevance = bVisible;
        Result.bDynamicRelevance = true;
        Result.bShadowRelevance = IsShadowCast(View);
        if(bInManipulation)
        {
            Result.bEditorNoDepthTestPrimitiveRelevance = true;
        }
        
        // Don't render on top in 'collision view' modes
        if(!bInCollisionView && !View->bIsGameView)
        {
            Result.bEditorPrimitiveRelevance = true;
        }
        
        return Result;
    }
    
    virtual void CreateRenderThreadResources() override
    {

        VertexFactory.InitResource();
        WireIndexBuffer.InitResource();
        WireVertexBuffer.InitResource();

        
    }
    
    virtual uint32 GetMemoryFootprint( void ) const override { return( sizeof( *this ) + GetAllocatedSize() ); }
    uint32 GetAllocatedSize( void ) const { return( FPrimitiveSceneProxy::GetAllocatedSize() ); }
    
private:

    FLocalVertexFactory VertexFactory;
    FPBModelWireIndexBuffer WireIndexBuffer;
    FPBModelWireVertexBuffer WireVertexBuffer;

    
    uint32 bVolume : 1;
    uint32 bBuilder : 1;	
    uint32 bSolidWhenSelected : 1;
    uint32 bInManipulation : 1;
    
    FColor BrushColor;
    FLinearColor LevelColor;
    FColor PropertyColor;
    
    /** Collision Response of this component**/
    UBodySetup* BodySetup;
    FCollisionResponseContainer CollisionResponse;
};