Node name clashes when exporting collision mesh to FBX

In FFbxExporter::ExportCollisionMesh, the mesh collision node name is set to “UCX_<parent-node-name>”, where the parent FBX node name in is just “0”, “1”, etc. for an InstancedStaticMeshComponent. The parent FBX node is in turn parented to a node with the instanced actor name, so the render geo is kept intact in FBX. But the collision node is placed directly under the root of the scene so results in a naming clash between different instanced actors.

The comment in code says:

// Collision meshes are added directly to the scene root, so we need to use the global transform instead of the relative one.

Do you know why this is so? In Perforce, I could not trace this further back than a large integration back in 2020.

Thanks,

Manu.

[Attachment Removed]

Steps to Reproduce

  • In the level, select multiple instanced static mesh actors that use different static meshes.
  • From the “File” menu, choose “Export Selected”
  • Pick an FBX file name to export to.
  • In the options dialog, check the “Collision” option.
  • Open the FBX file in Maya or Motion Builder
  • Observe that only one “UCX_0” is available, but we expect collision for _all_ selected actors to be exported.

[Attachment Removed]

Hi [mention removed]​,

I’ve tested all the steps you mentioned, and in my case the models are being imported correctly. I tested two different scenarios:

  • Actors that have 3 Instanced Meshes each

[Image Removed]

  • 3 Static Mesh Actors using different meshes

[Image Removed]

In both cases, Unreal is exporting the meshes with the collision for each instance. Because of that, it seems there might be something in the project setup that’s preventing the export from working correctly on your side.

I’d recommend double-checking that all the Static Meshes have their collision set up properly. In your second image, for example, the Axis_Guide object doesn’t have any collision, which is why it isn’t being exported.

If possible, having a small reproduction project that we can use to debug the issue would be very helpful. I tested this both in Maya and Blender and got the same results in both.

Best Regards,

Joan

[Attachment Removed]

Update. I tried this again using a standalone level to test, and it worked as in your example, Joan. So it must be something different we are doing in the export workflow I tested earlier. In any case, our artists would want nodes at the top level named to reflect the actor name, so I’ve made a change on our end to replicate the actor node hierarchy in the UCX_* node hierarchy as well, i.e. this variant of FFbxExporter::ExportCollisionMesh:

	FString MeshCollisionPrefix = TEXT("UCX_");
	FbxNode* SceneRootNode = Scene->GetRootNode();
 
	// Disambiguate the collision node name by walking up the ParentActor node hierarchy
	// and constructing a node matching each level of the hierarchy.
	TArray<FbxNode*> HierarchyNodes;
	for (FbxNode* AncestorNode = ParentActor;
		 (AncestorNode != nullptr) && (AncestorNode != SceneRootNode);
		 AncestorNode = AncestorNode->GetParent())
	{
		// Reverse the order so that the topmost node is first in the array.
		HierarchyNodes.Insert(AncestorNode, 0);
	}
 
	// Now walk back down the hierarchy.
	FbxNode* CurRenderParentNode = SceneRootNode;
	FbxNode* CurCollisionParentNode = SceneRootNode;
	FString UniqueMeshCollisionName = TEXT("UCX");
	for (FbxNode* CurNode : HierarchyNodes)
	{
		FString CurCollisionNodeName = MeshCollisionPrefix + UTF8_TO_TCHAR(CurNode->GetName());
		FbxNode* CurCollisionNode = CurCollisionParentNode->FindChild(TCHAR_TO_UTF8(*CurCollisionNodeName));
		if (CurCollisionNode == nullptr)
		{
			CurCollisionNode = FbxNode::Create(Scene, TCHAR_TO_UTF8(*CurCollisionNodeName));
			// Replicate local transform at each level of the hierarchy
			// to ensure the collision node is in the same position as the original mesh.
			// But for the root-level node, use the global transform just in case.
			if (CurRenderParentNode == SceneRootNode)
			{
				FbxAMatrix& GlobalTransform = CurNode->EvaluateGlobalTransform();
				CurCollisionNode->LclTranslation.Set(GlobalTransform.GetT());
				CurCollisionNode->LclRotation.Set(GlobalTransform.GetR());
				CurCollisionNode->LclScaling.Set(GlobalTransform.GetS());
			}
			else
			{
				CurCollisionNode->LclTranslation.Set(CurNode->LclTranslation);
				CurCollisionNode->LclRotation.Set(CurNode->LclRotation);
				CurCollisionNode->LclScaling.Set(CurNode->LclScaling);
			}
			CurCollisionParentNode->AddChild(CurCollisionNode);
		}
		CurCollisionParentNode = CurCollisionNode;
		CurRenderParentNode = CurNode;
		UniqueMeshCollisionName += FString(TEXT("_")) + UTF8_TO_TCHAR(CurNode->GetName());
	}
 
	FbxNode* LeafNode = CurCollisionParentNode;
	FbxMesh* CollisionMesh = FbxCollisionMeshes.FindRef(StaticMesh);
	if (!CollisionMesh)
	{
		CollisionMesh = FbxMesh::Create(Scene, TCHAR_TO_UTF8(*UniqueMeshCollisionName));
		//Export all collision elements in one mesh
		FbxSurfaceMaterial* FbxMaterial = nullptr;
		int32 ActualMatIndex = LeafNode->AddMaterial(FbxMaterial);
		FCollisionFbxExporter CollisionFbxExporter(StaticMesh, CollisionMesh, ActualMatIndex);
		CollisionFbxExporter.ExportCollisions();
		FbxCollisionMeshes.Add(StaticMesh, CollisionMesh);
	}
 
	//Set the original meshes in case it was already existing
	LeafNode->SetNodeAttribute(CollisionMesh);
	return LeafNode;

It seems to be working as we need, so this ticket sounds good to close.

Thanks!

[Attachment Removed]

Thanks Joan, for trying that out. In our case, we don’t see those “.._ncl…” nodes for some reason. We will investigate further, then.

Thanks,

Manu.

[Attachment Removed]

Perfect Manu!

I’ll let the case open for now. If you have any relevant information please update this thread. If I find any revelent to this topic I’ll do the same.

Best Regards,

Joan

[Attachment Removed]

Sounds good Joan, thanks again.

From a quick look, I don’t see anything in our export code path that goes through any code I could find that handles name clashes using “_ncl…”. I export using the standard FBX exporter (we don’t use Interchange, if that matters). That said, I’ll investigate a bit further on my end.

Manu.

[Attachment Removed]

Great. Just to confirm, I tested this with the binary 5.6 version of Unreal. Just to be sure there is no mistake with this. If you are using a source version that should also not be a problem is you are using the Standard FBX exporter.

[Attachment Removed]

Yes, we are on 5.6 as well (taken through Perforce) - we have several changes to the engine code but I haven’t yet noticed any relevant changes to FBX export code. I’ll investigate further.

[Attachment Removed]

Happy to hear Manu! :smiley:

Thanks for sharing the info, will close the case for now then.

Thanks!

[Attachment Removed]