Static Mesh Utility C++ Code
The following is my static mesh utility code to support runtime static mesh and hierarchical instanced static mesh. The code has been tested and runs correctly at packaged build time which is the difficult part to code since epic’s build time codebase is different to its editor.
Ensure you have these dependencies in your .build.cs file
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "RenderCore", "InputCore" });
PrivateDependencyModuleNames.AddRange(new string[] { "Json", "JsonUtilities", "MeshDescription", "StaticMeshDescription", "MeshConversion" });
#pragma once
#include "CoreMinimal.h"
#include "FStaticMeshData.generated.h"
USTRUCT()
struct FStaticMeshData
{
GENERATED_BODY()
public:
TArray<TArray<FVector3f>> Vertices; // Vertices per LOD
TArray<TArray<uint32>> PolygonGroupTriSizes; // Array of polygon group triangle counts per LOD, The array size is the number of polygon groups for each LOD
TArray<TArray<uint32>> TriangleIndices; // Triangle Indices per LOD
TArray<TArray<FVector3f>> Normals; // Normals per LOD
TArray<TArray<FVector3f>> Tangents; // Tangents per LOD
TArray<TArray<FColor>> Colors; // Vertex colors per LOD
TArray<uint32> UVLayers; // Number of UV Layers per LOD
TArray<TArray<FVector2f>> UV0; // UV0s per LOD
TArray<TArray<FVector2f>> UV1; // UV1s per LOD
TArray<uint32> MaterialIndexes; // Material index order for LOD 0 - assumes all other lods are the same
bool AutoLODScreenSizes; // Automatically calculate LOD screen sizes
TArray<float> LODScreenSizes; // Screen size per LOD
int32 CollisionType; // 0 - No collider, 1 - Simple box collider, 2 - Mesh collider
// Custom serialization function
friend FArchive& operator<<(FArchive& Ar, FStaticMeshData& MyStruct)
{
Ar << MyStruct.Vertices;
Ar << MyStruct.PolygonGroupTriSizes;
Ar << MyStruct.TriangleIndices;
Ar << MyStruct.Normals;
Ar << MyStruct.Tangents;
Ar << MyStruct.Colors;
Ar << MyStruct.UVLayers;
Ar << MyStruct.UV0;
Ar << MyStruct.UV1;
Ar << MyStruct.MaterialIndexes;
Ar << MyStruct.AutoLODScreenSizes;
Ar << MyStruct.LODScreenSizes;
Ar << MyStruct.CollisionType;
return Ar;
}
};
#pragma once
#include "FStaticMeshData.h"
#include "Components/HierarchicalInstancedStaticMeshComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Model/FVTransform.h"
#include "Engine/StaticMesh.h"
class SMUtility
{
public:
static TArray<uint8> GetStaticMeshAsByteArray(const UStaticMesh* StaticMesh, FStaticMeshData& MeshData);
static void GetMeshData(const UStaticMesh* StaticMesh, FStaticMeshData& MeshData);
static void DeserializeSM(UWorld* World, UStaticMeshComponent* SMComp, FStaticMeshData MeshData, TArray<FStaticMaterial> Materials);
static void DeserializeHISM(UWorld* World, UHierarchicalInstancedStaticMeshComponent* HISMComp, FStaticMeshData MeshData, TArray<FVTransform> InstanceTransforms, TArray<UMaterialInstanceDynamic*> Materials);
static void CheckLODSettings(UStaticMesh* StaticMesh);
private:
static void GetVertexBufferData(const UStaticMesh* StaticMesh, TArray<TArray<FVector3f>>& OutVertexArray, int32 LODCount);
static void GetTriangleIndices(const UStaticMesh* StaticMesh, TArray<TArray<uint32>>& OutPolygonGroupTriSizes, TArray<TArray<uint32>>& OutTriangleIndices, TArray<uint32>& OutMaterialIndexes, int32 LODCount);
static void GetNormalsTangents(const UStaticMesh* StaticMesh, TArray<TArray<FVector3f>>& OutNormals, TArray<TArray<FVector3f>>& OutTangents, int32 LODCount);
static void GetColors(const UStaticMesh* StaticMesh, TArray<TArray<FColor>>& OutColors, int32 LODCount);
static void GetUVLayers(const UStaticMesh* StaticMesh, TArray<uint32>& OutUVLayers, int32 LODCount);
static void GetUVs(const UStaticMesh* StaticMesh, TArray<uint32> OutUVLayers, TArray<TArray<FVector2f>>& OutUVs, uint32 channel, int32 LODCount);
static void GetLODScreenSizes(const UStaticMesh* StaticMesh, bool& OutAutoLODScreenSizes, TArray<float>& OutScreenSizes);
static void GetCollisionType(const UStaticMesh* StaticMesh, int32& OutCollisionType);
};
#include "SMUtility.h"
#include "MaterialDomain.h"
#include "MeshDescription.h"
#include "MeshDescriptionBuilder.h"
#include "StaticMeshAttributes.h"
#include "StaticMeshResources.h"
#include "UVStaticMesh.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/CollisionProfile.h"
#include "Engine/StaticMesh.h"
#include "Model/FVTransform.h"
#include "PhysicsEngine/BodySetup.h"
#if WITH_EDITOR
/**
* Get the static mesh data byte array
* @param StaticMesh - Static mesh
* @param MeshData - Mesh data
* @return Static mesh byte array
*/
TArray<uint8> SMUtility::GetStaticMeshAsByteArray(const UStaticMesh* StaticMesh, FStaticMeshData& MeshData)
{
GetMeshData(StaticMesh, MeshData);
TArray<uint8> SaveData;
FMemoryWriter MemoryWriter(SaveData, true);
MemoryWriter << MeshData;
return SaveData;
}
// Get the static mesh data and assign to the static mesh data ustruct
void SMUtility::GetMeshData(const UStaticMesh* StaticMesh, FStaticMeshData& MeshData)
{
if (!StaticMesh)
{
return;
}
// Find the static mesh LOD count
int32 LODCount = StaticMesh->GetNumLODs();
GetVertexBufferData(StaticMesh, MeshData.Vertices, LODCount);
GetTriangleIndices(StaticMesh, MeshData.PolygonGroupTriSizes, MeshData.TriangleIndices, MeshData.MaterialIndexes, LODCount);
GetNormalsTangents(StaticMesh, MeshData.Normals, MeshData.Tangents, LODCount);
GetColors(StaticMesh, MeshData.Colors, LODCount);
GetUVLayers(StaticMesh, MeshData.UVLayers, LODCount);
GetUVs(StaticMesh, MeshData.UVLayers, MeshData.UV0, 0, LODCount);
GetUVs(StaticMesh, MeshData.UVLayers, MeshData.UV1, 1, LODCount);
GetLODScreenSizes(StaticMesh, MeshData.AutoLODScreenSizes, MeshData.LODScreenSizes);
GetCollisionType(StaticMesh, MeshData.CollisionType);
}
// Get the vertex array from the static mesh
void SMUtility::GetVertexBufferData(const UStaticMesh* StaticMesh, TArray<TArray<FVector3f>>& OutVertexArray, const int32 LODCount)
{
// Allocate memory for vertices for all LODs
OutVertexArray.SetNum(LODCount);
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FPositionVertexBuffer& VertexBuffer = LODResources.VertexBuffers.PositionVertexBuffer;
OutVertexArray[LODIndex].SetNum(VertexBuffer.GetNumVertices());
// Iterate through the vertex buffer and extract vertex positions
for (uint32 i = 0; i < VertexBuffer.GetNumVertices(); i++)
OutVertexArray[LODIndex][i] = VertexBuffer.VertexPosition(i);
}
}
// Get the triangle index array for each polygon group from the static mesh
void SMUtility::GetTriangleIndices(const UStaticMesh* StaticMesh, TArray<TArray<uint32>>& OutPolygonGroupTriSizes, TArray<TArray<uint32>>& OutTriangleIndices, TArray<uint32>& OutMaterialIndexes, const int32 LODCount)
{
// Allocate polygon group tri size array for all LODs
OutPolygonGroupTriSizes.SetNum(LODCount);
// Allocate triangle indices array for all LODs
OutTriangleIndices.SetNum(LODCount);
// Allocate material indexes
OutMaterialIndexes.SetNum(StaticMesh->GetRenderData()->LODResources[0].Sections.Num());
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FIndexArrayView& IndexArrayView = LODResources.IndexBuffer.GetArrayView();
// Ensure the output array is empty before copying
OutPolygonGroupTriSizes[LODIndex].SetNum(LODResources.Sections.Num());
OutTriangleIndices[LODIndex].SetNum(IndexArrayView.Num());
int32 Index = 0;
// For each polygon group, copy the triangle indices to the triangle indices array
for (int32 SectionIndex = 0; SectionIndex < LODResources.Sections.Num(); SectionIndex++)
{
const FStaticMeshSection& Section = LODResources.Sections[SectionIndex];
if (LODIndex == 0)
OutMaterialIndexes[SectionIndex] = Section.MaterialIndex;
OutPolygonGroupTriSizes[LODIndex][SectionIndex] = Section.NumTriangles;
// Get the start and end index for the polygon group section
int32 StartIndex = Section.FirstIndex;
int32 EndIndex = Section.FirstIndex + Section.NumTriangles * 3;
for (int32 i = StartIndex; i < EndIndex; i++)
{
OutTriangleIndices[LODIndex][Index] = IndexArrayView[i];
Index++;
}
}
}
}
// Get the normals from the static mesh
void SMUtility::GetNormalsTangents(const UStaticMesh* StaticMesh, TArray<TArray<FVector3f>>& OutNormals, TArray<TArray<FVector3f>>& OutTangents, const int32 LODCount)
{
OutNormals.SetNum(LODCount);
OutTangents.SetNum(LODCount);
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FStaticMeshVertexBuffer& VertexBuffer = LODResources.VertexBuffers.StaticMeshVertexBuffer;
// Ensure the output array is empty before copying
OutNormals[LODIndex].SetNum(VertexBuffer.GetNumVertices());
OutTangents[LODIndex].SetNum(VertexBuffer.GetNumVertices());
// Copy the normals to the output array
for (uint32 i = 0; i < VertexBuffer.GetNumVertices(); i++)
{
OutNormals[LODIndex][i] = VertexBuffer.VertexTangentZ(i);
OutTangents[LODIndex][i] = VertexBuffer.VertexTangentX(i);
}
}
}
// Get the color array from the static mesh
void SMUtility::GetColors(const UStaticMesh* StaticMesh, TArray<TArray<FColor>>& OutColors, const int32 LODCount)
{
OutColors.SetNum(LODCount);
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FColorVertexBuffer& ColorVertexBuffer = LODResources.VertexBuffers.ColorVertexBuffer;
// Ensure the output array is empty before copying
OutColors[LODIndex].SetNum(ColorVertexBuffer.GetNumVertices());
// Copy the vertex colors to the output array
for (uint32 i = 0; i < ColorVertexBuffer.GetNumVertices(); ++i)
OutColors[LODIndex][i] = ColorVertexBuffer.VertexColor(i);
}
}
// Get the uv layers for all LODs
void SMUtility::GetUVLayers(const UStaticMesh* StaticMesh, TArray<uint32>& OutUVLayers, const int32 LODCount)
{
OutUVLayers.SetNum(LODCount);
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FStaticMeshVertexBuffer& VertexBuffer = LODResources.VertexBuffers.StaticMeshVertexBuffer;
OutUVLayers[LODIndex] = VertexBuffer.GetNumTexCoords();
}
}
// Get the UV channel array from the static mesh all LODs
void SMUtility::GetUVs(const UStaticMesh* StaticMesh, TArray<uint32> OutUVLayers, TArray<TArray<FVector2f>>& OutUVs, const uint32 channel, const int32 LODCount)
{
OutUVs.SetNum(LODCount);
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
const FStaticMeshLODResources& LODResources = StaticMesh->GetRenderData()->LODResources[LODIndex];
const FStaticMeshVertexBuffer& VertexBuffer = LODResources.VertexBuffers.StaticMeshVertexBuffer;
if (channel < OutUVLayers[LODIndex])
{
OutUVs[LODIndex].SetNum(VertexBuffer.GetNumVertices());
// Get the vertex UVs for a specific channel
for (uint32 i = 0; i < VertexBuffer.GetNumVertices(); i++)
OutUVs[LODIndex][i] = VertexBuffer.GetVertexUV(i, channel);
}
else
{
OutUVs[LODIndex].SetNum(0);
}
}
}
void SMUtility::GetLODScreenSizes(const UStaticMesh* StaticMesh, bool& OutAutoLODScreenSizes, TArray<float>& OutScreenSizes)
{
const TArray<FStaticMeshSourceModel>& SourceModels = StaticMesh->GetSourceModels();
OutAutoLODScreenSizes = StaticMesh->bAutoComputeLODScreenSize == 1;
if (OutAutoLODScreenSizes)
{
OutScreenSizes.SetNum(0);
}
else
{
OutScreenSizes.SetNum(SourceModels.Num());
for (int32 LODIndex = 0; LODIndex < SourceModels.Num(); LODIndex++)
{
OutScreenSizes[LODIndex] = SourceModels[LODIndex].ScreenSize.GetValue();
//UE_LOG(LogTemp, Log, TEXT("LOD Index: %d Screen Size : %f"), LODIndex, SourceModels[LODIndex].ScreenSize.GetValue());
}
}
//UE_LOG(LogTemp, Log, TEXT("Auto compute screen size : %s"), OutAutoLODScreenSizes ? TEXT("true") : TEXT("false"));
}
void SMUtility::GetCollisionType(const UStaticMesh* StaticMesh, int32& OutCollisionType)
{
// Simple box or sphere collision
if(StaticMesh->GetBodySetup()->AggGeom.BoxElems.Num() == 1 || StaticMesh->GetBodySetup()->AggGeom.SphereElems.Num() == 1)
OutCollisionType = 1;
else
// Consider multiple aggregated simple geometry as complex
if (StaticMesh->GetBodySetup()->AggGeom.BoxElems.Num() > 0 ||
StaticMesh->GetBodySetup()->AggGeom.SphereElems.Num() > 0 ||
StaticMesh->GetBodySetup()->AggGeom.SphylElems.Num() > 0 ||
StaticMesh->GetBodySetup()->AggGeom.ConvexElems.Num() > 0)
OutCollisionType = 2;
// Complex as simple is complex
else if (StaticMesh->GetBodySetup()->CollisionTraceFlag == CTF_UseComplexAsSimple)
OutCollisionType = 2;
else
OutCollisionType = 0;
}
#endif
/**
* Deserialize static mesh - both editor and package build
* @param World - Reference to the world
* @param SMComp - Static mesh component
* @param MeshData - Static mesh data
* @param Materials - Static material array
*/
void SMUtility::DeserializeSM(UWorld* World, UStaticMeshComponent* SMComp, FStaticMeshData MeshData, TArray<FStaticMaterial> Materials)
{
int32 LODCount = MeshData.Vertices.Num();
// Create static mesh
UVStaticMesh* StaticMesh = NewObject<UVStaticMesh>(GetTransientPackage(), NAME_None, RF_Public | RF_Standalone);
StaticMesh->SetWorld(World);
// Add default materials and material slot names
TArray<FName> MaterialSlotNames;
for (int MatIndex = 0; MatIndex < Materials.Num(); MatIndex++)
{
FName MaterialSlotName = StaticMesh->AddMaterial(UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface));
MaterialSlotNames.Add(MaterialSlotName);
}
// LOD mesh description array to contain the geometry, uv, normals, tangents, etc.
FMeshDescription meshDescriptions[6];
// Iterate through the lod index
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
// Register the static mesh attributes
FStaticMeshAttributes Attributes(meshDescriptions[LODIndex]);
Attributes.Register();
// Setup a mesh description builder
FMeshDescriptionBuilder meshDescBuilder;
meshDescBuilder.SetMeshDescription(&meshDescriptions[LODIndex]);
meshDescBuilder.EnablePolyGroups();
meshDescBuilder.SetNumUVLayers(MeshData.UVLayers[LODIndex]);
// Allocate vertices
TArray<FVertexID> VertexIDs;
VertexIDs.SetNum(MeshData.Vertices[LODIndex].Num());
for (int i = 0; i < VertexIDs.Num(); i++)
VertexIDs[i] = meshDescBuilder.AppendVertex(FVector(MeshData.Vertices[LODIndex][i]));
// Allocate vertex instances (3 per face)
TArray<FVertexInstanceID> VertexInsts;
for (int i = 0; i < VertexIDs.Num(); i++)
{
FVertexInstanceID instance = meshDescBuilder.AppendInstance(VertexIDs[i]);
meshDescBuilder.SetInstanceTangentSpace(instance, FVector(MeshData.Normals[LODIndex][VertexIDs[i]]), FVector(MeshData.Tangents[LODIndex][VertexIDs[i]]), 1);
if (MeshData.UVLayers[LODIndex] == 1)
{
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV0[LODIndex][VertexIDs[i]]), 0);
}
else if (MeshData.UVLayers[LODIndex] == 2)
{
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV0[LODIndex][VertexIDs[i]]), 0);
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV1[LODIndex][VertexIDs[i]]), 1);
}
VertexInsts.Add(instance);
}
// Allocate a polygon groups
FPolygonGroupID PolygonGroupIDs[10];
for (int i = 0; i < Materials.Num(); i++)
PolygonGroupIDs[i] = meshDescBuilder.AppendPolygonGroup();
// Add triangle indices by polygon group id
uint32 EndIndex = 0;
for (int32 PolyGroupIndex = 0; PolyGroupIndex < MeshData.PolygonGroupTriSizes[LODIndex].Num(); PolyGroupIndex++)
{
const uint32 StartIndex = EndIndex;
EndIndex = EndIndex + MeshData.PolygonGroupTriSizes[LODIndex][PolyGroupIndex] * 3;
for (uint32 i = StartIndex; i < EndIndex; i += 3)
{
meshDescBuilder.AppendTriangle(
VertexInsts[MeshData.TriangleIndices[LODIndex][i]],
VertexInsts[MeshData.TriangleIndices[LODIndex][i + 1]],
VertexInsts[MeshData.TriangleIndices[LODIndex][i + 2]],
PolygonGroupIDs[MeshData.MaterialIndexes[PolyGroupIndex]]);
}
}
// Assign the material slot names to the polygon group attributes
for (FPolygonGroupID PolygonGroupID : meshDescriptions[LODIndex].PolygonGroups().GetElementIDs())
{
meshDescriptions[LODIndex].PolygonGroupAttributes().SetAttribute(PolygonGroupID, MeshAttribute::PolygonGroup::ImportedMaterialSlotName, 0, MaterialSlotNames[PolygonGroupID.GetValue()]);
}
}
// Create the mesh description pointer array for each LOD
TArray<const FMeshDescription*> MeshDescPtrs;
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
MeshDescPtrs.Add(&meshDescriptions[LODIndex]);
// Setup a static mesh for rendering
StaticMesh->CreateBodySetup();
StaticMesh->InitResources();
// Build the static mesh
UStaticMesh::FBuildMeshDescriptionsParams mdParams;
mdParams.bBuildSimpleCollision = MeshData.CollisionType == 1;
mdParams.bAllowCpuAccess = true;
mdParams.bFastBuild = true;
StaticMesh->NeverStream = true;
StaticMesh->bAllowCPUAccess = true;
StaticMesh->BuildFromMeshDescriptions(MeshDescPtrs, mdParams);
// Set the LOD screen sizes
if (!MeshData.AutoLODScreenSizes)
{
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
StaticMesh->GetRenderData()->ScreenSize[LODIndex] = MeshData.LODScreenSizes[LODIndex];
}
// Create a complex collision for mesh
if (MeshData.CollisionType == 2)
{
StaticMesh->GetBodySetup()->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
StaticMesh->GetBodySetup()->CreatePhysicsMeshes();
SMComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
// Assign the materials to the static mesh in the correct index order
StaticMesh->SetStaticMaterials(Materials);
// Set the static mesh on the component
SMComp->SetStaticMesh(StaticMesh);
}
/**
* Deserialize hierarchical instance static mesh component - both editor and package build
* @param World - World reference
* @param HISMComp - Hierarchical instance static mesh component
* @param MeshData - Static mesh data
* @param InstanceTransforms - Instance transforms
* @param Materials - Static material array
*/
void SMUtility::DeserializeHISM(UWorld* World, UHierarchicalInstancedStaticMeshComponent* HISMComp, FStaticMeshData MeshData, TArray<FVTransform> InstanceTransforms, TArray<UMaterialInstanceDynamic*> Materials)
{
int32 LODCount = MeshData.Vertices.Num();
// Create static mesh
UVStaticMesh* StaticMesh = NewObject<UVStaticMesh>(GetTransientPackage(), NAME_None, RF_Public | RF_Standalone);
StaticMesh->SetWorld(World);
// Add default materials and material slot names
TArray<FName> MaterialSlotNames;
for (int32 MatIndex = 0; MatIndex < Materials.Num(); MatIndex++)
{
FName MaterialSlotName = StaticMesh->AddMaterial(UMaterial::GetDefaultMaterial(MD_Surface));
MaterialSlotNames.Add(MaterialSlotName);
}
// LOD mesh description array to contain the geometry, uv, normals, tangents, etc.
FMeshDescription meshDescriptions[6];
// Iterate through the lod index
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
{
// Register the static mesh attributes
FStaticMeshAttributes Attributes(meshDescriptions[LODIndex]);
Attributes.Register();
// Setup a mesh description builder
FMeshDescriptionBuilder meshDescBuilder;
meshDescBuilder.SetMeshDescription(&meshDescriptions[LODIndex]);
meshDescBuilder.EnablePolyGroups();
meshDescBuilder.SetNumUVLayers(MeshData.UVLayers[LODIndex]);
// Allocate vertices
TArray<FVertexID> VertexIDs;
VertexIDs.SetNum(MeshData.Vertices[LODIndex].Num());
for (int i = 0; i < VertexIDs.Num(); i++)
VertexIDs[i] = meshDescBuilder.AppendVertex(FVector(MeshData.Vertices[LODIndex][i]));
// Allocate vertex instances (3 per face)
TArray<FVertexInstanceID> VertexInsts;
for (int i = 0; i < VertexIDs.Num(); i++)
{
FVertexInstanceID instance = meshDescBuilder.AppendInstance(VertexIDs[i]);
meshDescBuilder.SetInstanceTangentSpace(instance, FVector(MeshData.Normals[LODIndex][VertexIDs[i]]), FVector(MeshData.Tangents[LODIndex][VertexIDs[i]]), 1);
if (MeshData.UVLayers[LODIndex] == 1)
{
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV0[LODIndex][VertexIDs[i]]), 0);
}
else if (MeshData.UVLayers[LODIndex] == 2)
{
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV0[LODIndex][VertexIDs[i]]), 0);
meshDescBuilder.SetInstanceUV(instance, FVector2D(MeshData.UV1[LODIndex][VertexIDs[i]]), 1);
}
VertexInsts.Add(instance);
}
// Allocate a polygon groups
FPolygonGroupID PolygonGroupIDs[10];
for (int i = 0; i < Materials.Num(); i++)
PolygonGroupIDs[i] = meshDescBuilder.AppendPolygonGroup();
// Add triangle indices by polygon group id
uint32 EndIndex = 0;
for (int32 PolyGroupIndex = 0; PolyGroupIndex < MeshData.PolygonGroupTriSizes[LODIndex].Num(); PolyGroupIndex++)
{
const uint32 StartIndex = EndIndex;
EndIndex = EndIndex + MeshData.PolygonGroupTriSizes[LODIndex][PolyGroupIndex] * 3;
for (uint32 i = StartIndex; i < EndIndex; i += 3)
{
meshDescBuilder.AppendTriangle(
VertexInsts[MeshData.TriangleIndices[LODIndex][i]],
VertexInsts[MeshData.TriangleIndices[LODIndex][i + 1]],
VertexInsts[MeshData.TriangleIndices[LODIndex][i + 2]],
PolygonGroupIDs[MeshData.MaterialIndexes[PolyGroupIndex]]);
}
}
// Assign the material slot names to the polygon group attributes
for (FPolygonGroupID PolygonGroupID : meshDescriptions[LODIndex].PolygonGroups().GetElementIDs())
{
meshDescriptions[LODIndex].PolygonGroupAttributes().SetAttribute(PolygonGroupID, MeshAttribute::PolygonGroup::ImportedMaterialSlotName, 0, MaterialSlotNames[PolygonGroupID.GetValue()]);
}
}
// Create the mesh description pointer array for each LOD
TArray<const FMeshDescription*> MeshDescPtrs;
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
MeshDescPtrs.Add(&meshDescriptions[LODIndex]);
// Setup a static mesh for rendering
StaticMesh->CreateBodySetup();
StaticMesh->InitResources();
// Build the static mesh
UStaticMesh::FBuildMeshDescriptionsParams mdParams;
mdParams.bBuildSimpleCollision = MeshData.CollisionType == 1;
mdParams.bAllowCpuAccess = true;
mdParams.bFastBuild = true;
StaticMesh->NeverStream = true;
StaticMesh->bAllowCPUAccess = true;
StaticMesh->BuildFromMeshDescriptions(MeshDescPtrs, mdParams);
// Set the LOD screen sizes
if (!MeshData.AutoLODScreenSizes)
{
for (int32 LODIndex = 0; LODIndex < LODCount; LODIndex++)
StaticMesh->GetRenderData()->ScreenSize[LODIndex] = MeshData.LODScreenSizes[LODIndex];
}
// Create a complex collision for mesh
if (MeshData.CollisionType == 2)
{
StaticMesh->GetBodySetup()->CollisionTraceFlag = ECollisionTraceFlag::CTF_UseComplexAsSimple;
StaticMesh->GetBodySetup()->CreatePhysicsMeshes();
HISMComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
}
// Set the static mesh on the component
HISMComp->SetStaticMesh(StaticMesh);
// Create the instance transforms
for (FVTransform transform : InstanceTransforms)
{
HISMComp->AddInstance(FTransform(
FRotator(transform.rotation.Y, transform.rotation.Z, transform.rotation.X),
FVector(transform.position), FVector(transform.scale)));
}
// Assign materials
for (int i = 0; i < Materials.Num(); i++)
HISMComp->SetMaterial(i, Materials[i]);
}
/**
* Check lod settings
* @param StaticMesh - Static mesh
*/
void SMUtility::CheckLODSettings(UStaticMesh* StaticMesh)
{
if (!StaticMesh)
{
UE_LOG(LogTemp, Warning, TEXT("Missing static mesh"));
return;
}
int32 NumLODs = StaticMesh->GetNumLODs();
UE_LOG(LogTemp, Log, TEXT("Number of LODs available: %d"), NumLODs);
for (int32 LODIndex = 0; LODIndex < NumLODs; LODIndex++)
{
UE_LOG(LogTemp, Log, TEXT("LOD %d ScreenSize: %f"), LODIndex, StaticMesh->GetRenderData()->ScreenSize[LODIndex].GetValue());
}
}
To support collisions in packaged build we create a UVStaticMesh which inherits from UStaticMesh. From here we can override the World and set the world.
#pragma once
#include "UVStaticMesh.generated.h"
UCLASS()
class VIRTUOSOCITYPRJ_API UVStaticMesh : public UStaticMesh
{
GENERATED_BODY()
public:
UVStaticMesh()
: World(nullptr)
{
bAllowCPUAccess = true;
}
virtual UWorld* GetWorld() const override { return World ? World : UStaticMesh::GetWorld(); }
void SetWorld(UWorld* InWorld) { World = InWorld; }
private:
UWorld* World;
};
Spawning Static Mesh Actors
// Define spawn location, rotation, and scale
FVector Location(ComponentData.transform.position);
FRotator Rotation(ComponentData.transform.rotation.Y, ComponentData.transform.rotation.Z, ComponentData.transform.rotation.X);
FVector Scale(ComponentData.transform.scale);
if (AStaticMeshActor* SpawnedActor = GetWorld()->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), FTransform(Rotation, Location, Scale), SpawnParams))
{
SpawnedActorMap.Add(SpawnedActor->GetUniqueID(), SpawnedActor);
SpawnedActor->GetRootComponent()->SetMobility(EComponentMobility::Type::Movable);
// Deserialize the byte array data
TArray<uint8> Data = <LOAD YOUR DATA FILE / HTTP ETC>;
FStaticMeshData MeshData;
if (!Data.IsEmpty())
{
// Deserialize the raw data into mesh data struct
FMemoryReader MemoryReader(Data, true);
MemoryReader << MeshData;
}
// Create the dynamic material instance array
TArray<FStaticMaterial> MaterialInstances;
// Create a material instance and supply its parent (master material)
UMaterialInstanceDynamic* MaterialInstance = UMaterialInstanceDynamic::Create(ParentMaterial, GetTransientPackage(), FName(material.name));
FStaticMaterial StaticMaterial = FStaticMaterial(MaterialInstance);
StaticMaterial.UVChannelData.bInitialized = true;
MaterialInstances.Add(StaticMaterial);
MaterialInstances.Add(StaticMaterial)
.. Add as many materials using the above approach as you need for the mesh
SMUtility::DeserializeSM(SpawnedActor->GetWorld(), SpawnedActor->GetRootComponent(), MeshData, MaterialInstances);
}
Hierarchical Instanced Static Mesh
UHierarchicalInstancedStaticMeshComponent* HISMComp = NewObject<UHierarchicalInstancedStaticMeshComponent>(this, TEXT("HISMComponent"));
if (HISMComp)
{
HISMComp->RegisterComponent();
HISMComp->AttachToComponent(Component, FAttachmentTransformRules::KeepRelativeTransform);
}
TArray<FVTransform> Transforms;
// Populate the transforms where your instances should be located
SMUtility::DeserializeHISM(World, HISMComp, MeshData, Transforms, MaterialInstances);
#pragma once
#include "CoreMinimal.h"
#include "FVTransform.generated.h"
USTRUCT()
struct FVTransform
{
GENERATED_BODY()
UPROPERTY()
FVector3d position;
UPROPERTY()
FVector3d rotation;
UPROPERTY()
FVector3d scale;
FVTransform()
: position(FVector3d::ZeroVector)
, rotation(FVector3d::ZeroVector)
, scale(FVector3d::ZeroVector)
{
}
FVTransform(const FVector3d& Position, const FVector3d& Rotation, const FVector3d& Scale):
position(Position),
rotation(Rotation),
scale(Scale)
{
}
};