Because of the bug with the RMC and Ray Tracing in 4.22 and 4.23, I need to take my runtime meshes and convert them to static meshes after they are finalized during play.
I have adapted the ClickedOnConvertToStaticMesh code from the RuntimeMeshComponentDetails.cpp file into the function at the bottom of this post. In BP, I do the following, in part:
Ideally, this takes a RMC and converts it to a Static Mesh Component, then deletes the RMC that created it. This happens for each RMC until there are no more RMCs, just Static Meshes.
Unfortunately, check(DoesSectionExist(SectionId)); in RuntimeMeshData.cpp throws an error after the first successful mesh conversion (it gets called by auto MeshData = RuntimeMeshComp->GetSectionReadonly(SectionIdx);). I get Assertion failed: DoesSectionExist(SectionId) (SectionID variable is optimized away and not available).
Does anyone have any ideas on why this is happening? I am confused why it works for the first mesh but crashes on the second, regardless of the mesh I use.
UStaticMesh* ULoaderBPFunctionLibrary::MakeStaticMesh(URuntimeMeshComponent* ProcMesh)
{
// Find first selected RuntimeMeshComp
URuntimeMeshComponent* RuntimeMeshComp = ProcMesh;
if (RuntimeMeshComp != nullptr)
{
FString Name;
FString ActorName = ProcMesh->GetOwner()->GetName();
FString LevelName = ProcMesh->GetWorld()->GetMapName();
FString AssetName = FString(TEXT("SM_")) + LevelName + FString(TEXT("_") + ActorName);
FString PathName = FString(TEXT("/Game/Meshes/"));
FString PackageName = PathName + AssetName;
// Raw mesh data we are filling in
FRawMesh RawMesh;
// Unique Materials to apply to new mesh
TArray<FStaticMaterial> Materials;
bool bUseHighPrecisionTangents = false;
bool bUseFullPrecisionUVs = false;
const int32 NumSections = RuntimeMeshComp->GetNumSections();
UE_LOG(LogTemp, Warning, TEXT("%d"), NumSections);
int32 VertexBase = 0;
int32 MaxUVs = 0;
for (int32 SectionIdx = 0; SectionIdx < NumSections; SectionIdx++)
{
auto MeshData = RuntimeMeshComp->GetSectionReadonly(SectionIdx);
check(MeshData.IsValid());
int32 NumUVs = MeshData->NumUVChannels();
MaxUVs = FMath::Max(NumUVs, MaxUVs);
// Fill out existing UV channels to start of this one
for (int32 Index = 0; Index < MaxUVs; Index++)
{
RawMesh.WedgeTexCoords[Index].SetNumZeroed(RawMesh.WedgeIndices.Num());
}
// Copy the vertex positions
int32 NumVertices = MeshData->NumVertices();
for (int32 Index = 0; Index < NumVertices; Index++)
{
RawMesh.VertexPositions.Add(MeshData->GetPosition(Index));
}
// Copy wedges
int32 NumTris = MeshData->NumIndices();
for (int32 Index = 0; Index < NumTris; Index++)
{
int32 VertexIndex = MeshData->GetIndex(Index);
RawMesh.WedgeIndices.Add(VertexIndex + VertexBase);
FVector TangentX = MeshData->GetTangent(VertexIndex);
FVector TangentZ = MeshData->GetNormal(VertexIndex);
FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal() * MeshData->GetNormal(VertexIndex).W;
RawMesh.WedgeTangentX.Add(TangentX);
RawMesh.WedgeTangentY.Add(TangentY);
RawMesh.WedgeTangentZ.Add(TangentZ);
for (int32 UVIndex = 0; UVIndex < NumUVs; UVIndex++)
{
RawMesh.WedgeTexCoords[UVIndex].Add(MeshData->GetUV(VertexIndex, UVIndex));
}
RawMesh.WedgeColors.Add(MeshData->GetColor(VertexIndex));
}
// Find a material index for this section.
UMaterialInterface* Material = RuntimeMeshComp->GetSectionMaterial(SectionIdx);
int32 MaterialIndex = Materials.AddUnique(FStaticMaterial(Material));
// copy face info
for (int32 TriIdx = 0; TriIdx < NumTris / 3; TriIdx++)
{
// Set the face material
RawMesh.FaceMaterialIndices.Add(MaterialIndex);
RawMesh.FaceSmoothingMasks.Add(0); // Assume this is ignored as bRecomputeNormals is false
}
// Update offset for creating one big index/vertex buffer
VertexBase += NumVertices;
}
// Fill out existing UV channels to start of this one
for (int32 Index = 0; Index < MaxUVs; Index++)
{
RawMesh.WedgeTexCoords[Index].SetNumZeroed(RawMesh.WedgeIndices.Num());
}
// If we got some valid data.
if (RawMesh.VertexPositions.Num() >= 3 && RawMesh.WedgeIndices.Num() >= 3)
{
// Then find/create it.
UPackage* Package = CreatePackage(NULL, *PackageName);
check(Package);
// Create StaticMesh object
UStaticMesh* StaticMesh = NewObject<UStaticMesh>(Package, FName(*AssetName), RF_Public | RF_Standalone);
StaticMesh->InitResources();
StaticMesh->LightingGuid = FGuid::NewGuid();
// Add source to new StaticMesh
FStaticMeshSourceModel* SrcModel = new (StaticMesh->SourceModels) FStaticMeshSourceModel();
SrcModel->BuildSettings.bRecomputeNormals = false;
SrcModel->BuildSettings.bRecomputeTangents = false;
SrcModel->BuildSettings.bRemoveDegenerates = false;
SrcModel->BuildSettings.bUseHighPrecisionTangentBasis = bUseHighPrecisionTangents;
SrcModel->BuildSettings.bUseFullPrecisionUVs = bUseFullPrecisionUVs;
SrcModel->BuildSettings.bGenerateLightmapUVs = true;
SrcModel->BuildSettings.SrcLightmapIndex = 0;
SrcModel->BuildSettings.DstLightmapIndex = 1;
SrcModel->RawMeshBulkData->SaveRawMesh(RawMesh);
// Set the materials used for this static mesh
StaticMesh->StaticMaterials = Materials;
int32 NumMaterials = StaticMesh->StaticMaterials.Num();
// Set up the SectionInfoMap to enable collision
for (int32 SectionIdx = 0; SectionIdx < NumMaterials; SectionIdx++)
{
FMeshSectionInfo Info = StaticMesh->SectionInfoMap.Get(0, SectionIdx);
Info.MaterialIndex = SectionIdx;
Info.bEnableCollision = true;
StaticMesh->SectionInfoMap.Set(0, SectionIdx, Info);
}
// Configure body setup for working collision.
StaticMesh->CreateBodySetup();
StaticMesh->BodySetup->CollisionTraceFlag = CTF_UseComplexAsSimple;
// Build mesh from source
StaticMesh->Build(false);
StaticMesh->PostEditChange();
// Notify asset registry of new asset
FAssetRegistryModule::AssetCreated(StaticMesh);
UE_LOG(LogTemp, Warning, TEXT("Returning static mesh"));
return StaticMesh;
}
else {
UE_LOG(LogTemp, Warning, TEXT("Returning NULL because no valid data"));
return NULL;
}
}
UE_LOG(LogTemp, Warning, TEXT("Returning NULL because empty object passed"));
return NULL;
}