In case anyone’s interested, I made a version of this class that uses FDynamicMeshBuilder to render the geometry. It’s a little different from the option proposed here in that it uses Vertex and Index buffers to store its geometry information rather than a triangle list, which in my opinion is both easier to define and certainly more efficient. It’s obviously incomplete and doesn’t have collision or blueprint hooks, I’m merely posting it here to show how you could do that and to spark discussion. Since FDynamicMeshBuilder doesn’t implement access to its buffers or a copy constructor, you cannot use it directly as a data storage device, so I had to duplicate some of its functionality.
Component header:
#pragma once
#include "Components/MeshComponent.h"
#include "DynamicMeshBuilder.h"
#include "DynamicMeshComponent.generated.h"
/**
*
*/
UCLASS()
class EMPTYTEST_API UDynamicMeshComponent : public UMeshComponent
{
GENERATED_BODY()
public:
// Begin UPrimitiveComponent interface.
virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
// End UPrimitiveComponent interface.
// Begin UMeshComponent interface.
virtual int32 GetNumMaterials() const override;
// End UMeshComponent interface.
// Begin DynamicMeshBuilder functionality
int32 AddVertex(
const FVector& InPosition,
const FVector2D& InTextureCoordinate,
const FVector& InTangentX,
const FVector& InTangentY,
const FVector& InTangentZ,
const FColor& InColor
);
int32 AddVertex(const FDynamicMeshVertex &InVertex);
int32 AddVertices(const TArray<FDynamicMeshVertex>& InVertices);
void AddTriangle(int32 V0,int32 V1,int32 V2);
void AddTriangles(const TArray<int32> &InIndices);
// End DynamicMeshBuilder functionality
TArray<FDynamicMeshVertex> GetVertices();
TArray<int32> GetIndices();
private:
// Begin USceneComponent interface.
virtual FBoxSphereBounds CalcBounds(const FTransform & LocalToWorld) const override;
// Begin USceneComponent interface.
TArray<FDynamicMeshVertex> _vertices;
TArray<int32> _indices;
friend class FDynamicMeshSceneProxy;
};
Component implementation:
#include "EmptyTest.h"
#include "DynamicMeshComponent.h"
class FDynamicMeshSceneProxy : public FPrimitiveSceneProxy
{
public:
FDynamicMeshSceneProxy(UDynamicMeshComponent* Component)
: FPrimitiveSceneProxy(Component),
MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel())),
_vertices(Component->GetVertices()),
_indices(Component->GetIndices())
{
// Grab material
Material = Component->GetMaterial(0);
if(Material == NULL)
{
Material = UMaterial::GetDefaultMaterial(MD_Surface);
}
}
virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
{
QUICK_SCOPE_CYCLE_COUNTER( STAT_DynamicMeshSceneProxy_GetDynamicMeshElements );
for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
{
if (VisibilityMap & (1 << ViewIndex))
{
FMaterialRenderProxy* MaterialProxy = Material->GetRenderProxy(IsSelected());
const FSceneView* View = Views[ViewIndex];
FDynamicMeshBuilder MeshBuilder;
MeshBuilder.AddVertices(_vertices);
MeshBuilder.AddTriangles(_indices);
MeshBuilder.GetMesh(GetLocalToWorld(), MaterialProxy, SDPG_World, false, false, ViewIndex, Collector);
}
}
}
virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View)
{
FPrimitiveViewRelevance Result;
Result.bDrawRelevance = IsShown(View);
Result.bShadowRelevance = IsShadowCast(View);
Result.bDynamicRelevance = true;
MaterialRelevance.SetPrimitiveViewRelevance(Result);
return Result;
}
virtual bool CanBeOccluded() const override
{
return !MaterialRelevance.bDisableDepthTest;
}
virtual uint32 GetMemoryFootprint(void) const
{
return(sizeof(*this) + GetAllocatedSize());
}
uint32 GetAllocatedSize(void) const
{
return(FPrimitiveSceneProxy::GetAllocatedSize());
}
private:
UMaterialInterface* Material;
TArray<FDynamicMeshVertex> _vertices;
TArray<int32> _indices;
FMaterialRelevance MaterialRelevance;
};
int32 UDynamicMeshComponent::AddVertex(
const FVector& InPosition,
const FVector2D& InTextureCoordinate,
const FVector& InTangentX,
const FVector& InTangentY,
const FVector& InTangentZ,
const FColor& InColor
)
{
int32 VertexIndex = _vertices.Num();
FDynamicMeshVertex* Vertex = new(_vertices) FDynamicMeshVertex;
Vertex->Position = InPosition;
Vertex->TextureCoordinate = InTextureCoordinate;
Vertex->TangentX = InTangentX;
Vertex->TangentZ = InTangentZ;
// store the sign of the determinant in TangentZ.W (-1=0,+1=255)
Vertex->TangentZ.Vector.W = GetBasisDeterminantSign( InTangentX, InTangentY, InTangentZ ) < 0 ? 0 : 255;
Vertex->Color = InColor;
return VertexIndex;
}
int32 UDynamicMeshComponent::AddVertex(const FDynamicMeshVertex &InVertex)
{
int32 VertexIndex = _vertices.Num();
FDynamicMeshVertex* Vertex = new(_vertices) FDynamicMeshVertex(InVertex);
return VertexIndex;
}
int32 UDynamicMeshComponent::AddVertices(const TArray<FDynamicMeshVertex>& InVertices)
{
int32 StartIndex = _vertices.Num();
_vertices.Append(InVertices);
return StartIndex;
}
void UDynamicMeshComponent::AddTriangle(int32 V0,int32 V1,int32 V2)
{
_indices.Add(V0);
_indices.Add(V1);
_indices.Add(V2);
}
void UDynamicMeshComponent::AddTriangles(const TArray<int32> &InIndices)
{
_indices.Append(InIndices);
}
TArray<FDynamicMeshVertex> UDynamicMeshComponent::GetVertices()
{
return _vertices;
}
TArray<int32> UDynamicMeshComponent::GetIndices()
{
return _indices;
}
FPrimitiveSceneProxy* UDynamicMeshComponent::CreateSceneProxy()
{
return new FDynamicMeshSceneProxy(this);
}
int32 UDynamicMeshComponent::GetNumMaterials() const
{
return 1;
}
FBoxSphereBounds UDynamicMeshComponent::CalcBounds(const FTransform & LocalToWorld) const
{
FBoxSphereBounds NewBounds;
NewBounds.Origin = FVector::ZeroVector;
NewBounds.BoxExtent = FVector(HALF_WORLD_MAX,HALF_WORLD_MAX,HALF_WORLD_MAX);
NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX));
return NewBounds;
}
Usage example (two-sided triangle):
UDynamicMeshComponent* mesh = ObjectInitializer.CreateDefaultSubobject<UDynamicMeshComponent>(this, TEXT("DynamicTriangle"));
static ConstructorHelpers::FObjectFinder<UMaterialInterface> material(TEXT("Material'/Game/Materials/BaseColor.BaseColor'"));
mesh->SetMaterial(0, material.Object);
FVector const vecX(1.f, 0.f, 0.f);
FVector const vecY(0.f, 1.f, 0.f);
FVector const vecZ(0.f, 0.f, 1.f);
mesh->AddVertex(FVector(0.f, 0.f, 0.f),FVector2D::ZeroVector, vecX, vecY, vecZ, FColor::Blue);
mesh->AddVertex(FVector(0.f, 100.f, 0.f),FVector2D::ZeroVector, vecX, vecY, vecZ, FColor::Blue);
mesh->AddVertex(FVector(0.f, 0.f, 100.f),FVector2D::ZeroVector, vecX, vecY, vecZ, FColor::Blue);
mesh->AddTriangle(0,1,2);
mesh->AddTriangle(0,2,1);
RootComponent = mesh;