Converting Runtime Mesh Component to Static Mesh

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

Hi, have you found a solution to convert RMC to a Static Mesh Component?

1 Like