Creating a custom Primitive Component

Primitive components are the most complex type of Actor Component because they not only have a transform, but are also rendered on screen.

How to do it...

  1. Create a custom C++ class based on MeshComponent. When Visual Studio loads, add the following to your class header file:
    UCLASS(ClassGroup=Experimental, meta = (BlueprintSpawnableComponent))
    public:
    virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
    TArray<int32> Indices;
    TArray<FVector> Vertices;
    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Materials)
    UMaterial* TheMaterial;
  2. We need to create an implementation for our overridden CreateSceneProxy function in our cpp file:
    FPrimitiveSceneProxy* UMyMeshComponent::CreateSceneProxy()
    {
      FPrimitiveSceneProxy* Proxy = NULL;
      Proxy = new FMySceneProxy(this);
      return Proxy;
    }
  3. This function returns an instance of FMySceneProxy, which we need to implement. Do so by adding the following code above the CreateSceneProxy function:
    class FMySceneProxy : public FPrimitiveSceneProxy
    {
      public:
      FMySceneProxy(UMyMeshComponent* Component)
      :FPrimitiveSceneProxy(Component),
      Indices(Component->Indices),
      TheMaterial(Component->TheMaterial)
      {
        VertexBuffer = FMyVertexBuffer();
        IndexBuffer = FMyIndexBuffer();
        for (FVector Vertex : Component->Vertices)
        {
          Vertices.Add(FDynamicMeshVertex(Vertex));
        }
      };
      UPROPERTY()
      UMaterial* TheMaterial;
      virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View)  const override
      {
        FPrimitiveViewRelevance Result;
        Result.bDynamicRelevance = true;
        Result.bDrawRelevance = true;
        Result.bNormalTranslucencyRelevance = true;
        return Result;
      }
      virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
      {
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
          FDynamicMeshBuilder MeshBuilder;
          if (Vertices.Num() == 0)
          {
            return;
          }
          MeshBuilder.AddVertices(Vertices);
          MeshBuilder.AddTriangles(Indices);
          MeshBuilder.GetMesh(FMatrix::Identity, new FColoredMaterialRenderProxy(TheMaterial->GetRenderProxy(false), FLinearColor::Gray), GetDepthPriorityGroup(Views[ViewIndex]), true, true, ViewIndex, Collector);
        }
      }
      uint32 FMySceneProxy::GetMemoryFootprint(void) const override
      {
        return sizeof(*this);
      }
      virtual ~FMySceneProxy() {};
      private:
      TArray<FDynamicMeshVertex> Vertices;
      TArray<int32> Indices;
      FMyVertexBuffer VertexBuffer;
      FMyIndexBuffer IndexBuffer;
    };
  4. Our scene proxy requires a vertex buffer and an index buffer. The following subclasses should be placed above the Scene Proxy's implementation:
    class FMyVertexBuffer : public FVertexBuffer
    {
      public:
      TArray<FVector> Vertices;
      virtual void InitRHI() override
      {
        FRHIResourceCreateInfo CreateInfo;
        VertexBufferRHI = RHICreateVertexBuffer(Vertices.Num() * sizeof(FVector), BUF_Static, CreateInfo);
        void* VertexBufferData = RHILockVertexBuffer(VertexBufferRHI, 0, Vertices.Num() * sizeof(FVector), RLM_WriteOnly);
        FMemory::Memcpy(VertexBufferData, Vertices.GetData(), Vertices.Num() * sizeof(FVector));
        RHIUnlockVertexBuffer(VertexBufferRHI);
      }
    };
    class FMyIndexBuffer : public FIndexBuffer
    {
      public:
      TArray<int32> Indices;
      virtual void InitRHI() override
      {
        FRHIResourceCreateInfo CreateInfo;
        IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), Indices.Num() * sizeof(int32), BUF_Static, CreateInfo);
        void* Buffer = RHILockIndexBuffer(IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
        FMemory::Memcpy(Buffer, Indices.GetData(), Indices.Num() * sizeof(int32));
        RHIUnlockIndexBuffer(IndexBufferRHI);
      }
    };
  5. Add the following constructor implementation:
    UMyMeshComponent::UMyMeshComponent()
    {
      static ConstructorHelpers::FObjectFinder<UMaterial> Material(TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial'"));
      if (Material.Object != NULL)
      {
        TheMaterial = (UMaterial*)Material.Object;
      }
      Vertices.Add(FVector(10, 0, 0));
      Vertices.Add(FVector(0, 10, 0));
      Vertices.Add(FVector(0, 0, 10));
      Indices.Add(0);
      Indices.Add(1);
      Indices.Add(2);
    }
  6. Verify that your code looks like the following:
    #pragma once
    
    #include "Components/MeshComponent.h"
    #include "MyMeshComponent.generated.h"
    
    UCLASS(ClassGroup = Experimental, meta = (BlueprintSpawnableComponent))
    class UE4COOKBOOK_API UMyMeshComponent : public UMeshComponent
    {
      GENERATED_BODY()
      public:
      virtual FPrimitiveSceneProxy* CreateSceneProxy() override;
      TArray<int32> Indices;
      TArray<FVector> Vertices;
    
      UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Materials)
      UMaterial* TheMaterial;
      UMyMeshComponent();
    };
    
    #include "UE4Cookbook.h"
    #include "MyMeshComponent.h"
    #include <VertexFactory.h>
    #include "DynamicMeshBuilder.h"
    
    class FMyVertexBuffer : public FVertexBuffer
    {
      public:
      TArray<FVector> Vertices;
    
      virtual void InitRHI() override
      {
        FRHIResourceCreateInfo CreateInfo;
        VertexBufferRHI = RHICreateVertexBuffer(Vertices.Num() * sizeof(FVector), BUF_Static, CreateInfo);
    
        void* VertexBufferData = RHILockVertexBuffer(VertexBufferRHI, 0, Vertices.Num() * sizeof(FVector), RLM_WriteOnly);
        FMemory::Memcpy(VertexBufferData, Vertices.GetData(), Vertices.Num() * sizeof(FVector));
        RHIUnlockVertexBuffer(VertexBufferRHI);
      }
    };
    
    class FMyIndexBuffer : public FIndexBuffer
    {
      public:
      TArray<int32> Indices;
    
      virtual void InitRHI() override
      {
        FRHIResourceCreateInfo CreateInfo;
        IndexBufferRHI = RHICreateIndexBuffer(sizeof(int32), Indices.Num() * sizeof(int32), BUF_Static, CreateInfo);
    
        void* Buffer = RHILockIndexBuffer(IndexBufferRHI, 0, Indices.Num() * sizeof(int32), RLM_WriteOnly);
        FMemory::Memcpy(Buffer, Indices.GetData(), Indices.Num() * sizeof(int32));
        RHIUnlockIndexBuffer(IndexBufferRHI);
      }
    };
    class FMySceneProxy : public FPrimitiveSceneProxy
    {
      public:
      FMySceneProxy(UMyMeshComponent* Component)
      :FPrimitiveSceneProxy(Component),
      Indices(Component->Indices),
      TheMaterial(Component->TheMaterial)
      {
        VertexBuffer = FMyVertexBuffer();
        IndexBuffer = FMyIndexBuffer();
    
        for (FVector Vertex : Component->Vertices)
        {
          Vertices.Add(FDynamicMeshVertex(Component->GetComponentLocation() + Vertex));
        }
      };
    
    UPROPERTY()
      UMaterial* TheMaterial;
    
      virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View)  const override
      {
        FPrimitiveViewRelevance Result;
        Result.bDynamicRelevance = true;
        Result.bDrawRelevance = true;
        Result.bNormalTranslucencyRelevance = true;
        return Result;
      }
    
      virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override
      {
        for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
        {
          FDynamicMeshBuilder MeshBuilder;
          if (Vertices.Num() == 0)
          {
            return;
          }
          MeshBuilder.AddVertices(Vertices);
          MeshBuilder.AddTriangles(Indices);
    
          MeshBuilder.GetMesh(FMatrix::Identity, new FColoredMaterialRenderProxy(TheMaterial->GetRenderProxy(false), FLinearColor::Gray), GetDepthPriorityGroup(Views[ViewIndex]), true, true, ViewIndex, Collector);
    
        }
      }
    
      void FMySceneProxy::OnActorPositionChanged() override
      {
        VertexBuffer.ReleaseResource();
        IndexBuffer.ReleaseResource();
      }
    
      uint32 FMySceneProxy::GetMemoryFootprint(void) const override
      {
        return sizeof(*this);
      }
      virtual ~FMySceneProxy() {};
      private:
      TArray<FDynamicMeshVertex> Vertices;
      TArray<int32> Indices;
      FMyVertexBuffer VertexBuffer;
      FMyIndexBuffer IndexBuffer;
    };
    
    FPrimitiveSceneProxy* UMyMeshComponent::CreateSceneProxy()
    {
      FPrimitiveSceneProxy* Proxy = NULL;
      Proxy = new FMySceneProxy(this);
      return Proxy;
    }
    
    UMyMeshComponent::UMyMeshComponent()
    {
      static ConstructorHelpers::FObjectFinder<UMaterial> Material(TEXT("Material'/Engine/BasicShapes/BasicShapeMaterial'"));
    
      if (Material.Object != NULL)
      {
        TheMaterial = (UMaterial*)Material.Object;
      }
      Vertices.Add(FVector(10, 0, 0));
      Vertices.Add(FVector(0, 10, 0));
      Vertices.Add(FVector(0, 0, 10));
      Indices.Add(0);
      Indices.Add(1);
      Indices.Add(2);
    }
  7. Create an empty Actor in the editor and add the new mesh component to it to see that your triangle is rendered. Experiment by changing the values added with Vertices. Add and see how the geometry changes after a recompile.
    How to do it...

How it works...

  1. In order for an Actor to be rendered, the data describing it needs to be made accessible to the rendering thread.
  2. The easiest way to do this is with a Scene Proxy—a proxy object that is created on the render thread, and is designed to provide thread safety to the data transfer.
  3. The PrimitiveComponent class defines a CreateSceneProxy function that returns FPrimitiveSceneProxy*. This function allows custom components like ours to return an object based on FPrimitiveSceneProxy, leveraging polymorphism.
  4. We define the constructor of the SceneProxy object to take in an instance of our component so that each SceneProxy created knows about the component instance it is associated with.
  5. That data is then cached in the Scene Proxy, and passed to the renderer using GetDynamicMeshElements.
  6. We create an IndexBuffer and a VertexBuffer. Each of the buffer classes we create are helpers that assist the Scene Proxy with allocating platform-specific memory for the two buffers. They do so in the InitRHI (also known as Initialize Render Hardware Interface) function, wherein they use functions from the RHI API to create a vertex buffer, lock it, copy the required data, and then unlock it.
  7. Inside the component's constructor, we look for a material asset that is built into the engine with the ObjectFinder template so that our mesh will have a material.
  8. We then add some vertices and indexes to our buffers so that the mesh can be drawn when the renderer requests a Scene Proxy.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.129.21.166