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;
};