[MUTABLE] Make node that merges two meshes and outputs the merged mesh

Hello, I am trying to create a node for Mutable which takes 2 skeletal meshes and merges them together, outputting the merged mesh. We need this as other tech we have a node for requires a full body and head, but we have separate meshes for each.

I currently already have a UMeshMerge : UCustomizableObjectNode node for the editor, a NodeMeshMerge : NodeMesh node for the intermediate node structures, and I’m using ASTOpFixed with an OP_TYPE of ME_MERGE. This works in the sense that mu::MeshMerge does run, but it crashes later in the compile step because its buffer set is missing MUTABLE_VERTEXBUFFER_TEXCOORDS (UnrealConversionUtils.cpp : 340 - const int NumTexCoords = MutableMeshVertexBuffers.GetBufferChannelCount(MUTABLE_VERTEXBUFFER_TEXCOORDS):wink:

I have a feeling that my ad-hoc approach is doing something wrong, so is there a defacto way to approach this? Or better yet, is there a way to do this already that I’m missing? I know that the MeshComponent nodes merge the meshes but it outputs a component and not the mesh itself so it isn’t very useful to us.

Here are the relevant code sections (posting two separate code snippets isnt working for me so sorry for the formatting, I have separated the two snippets by ======'s instead):

`mu::Ptrmu::NodeMesh GenerateMutableSourceMesh(const UEdGraphPin* Pin,
FMutableGraphGenerationContext& GenerationContext,
FMutableGraphMeshGenerationData& MeshData,
uint32 SurfaceMetadataId,
const bool bLinkedToExtendMaterial,
const bool bOnlyConnectedLOD)
{

else if (const UExtractMeshFromComponentNode* TypedNodeMeshMerge = Cast(Node))
{
mu::Ptrmu::NodeNSMeshMerge MeshMergeNode = new mu::NodeNSMeshMerge();
Result = MeshMergeNode;

if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMeshMerge->GetMesh0Pin()))
{
FMutableGraphMeshGenerationData ChildMeshData;
mu::Ptrmu::NodeMesh ChildNode = GenerateMutableSourceMesh(ConnectedPin, GenerationContext, ChildMeshData, SurfaceMetadataId, false, bOnlyConnectedLOD);
if (ChildNode)
{
MeshMergeNode->SetMesh0(ChildNode);
}
}

if (const UEdGraphPin* ConnectedPin = FollowInputPin(*TypedNodeMeshMerge->GetMesh1Pin()))
{
FMutableGraphMeshGenerationData ChildMeshData;
mu::Ptrmu::NodeMesh ChildNode = GenerateMutableSourceMesh(ConnectedPin, GenerationContext, ChildMeshData, SurfaceMetadataId, false, bOnlyConnectedLOD);
if (ChildNode)
{
MeshMergeNode->SetMesh1(ChildNode);
}
}
}

void CodeGenerator::GenerateMesh_NSMeshMerge(const FMeshGenerationOptions& InOptions, FMeshGenerationResult& OutResult, const NodeNSMeshMerge* MeshMerge)
{
const NodeNSMeshMerge::Private& Node = *MeshMerge->GetPrivate();
Ptr lastCompOp;
Ptr lastMeshOp;

Ptr mop = new ASTOpFixed();
mop->op.type = OP_TYPE::ME_MERGE;

FMeshGenerationOptions Opts;
Opts.bLayouts = true;

if (Node.Mesh0)
{
FMeshGenerationResult Mesh0Result;
GenerateMesh(Opts, Mesh0Result, Node.Mesh0);
mop->SetChild(mop->op.args.MeshMerge.base, Mesh0Result.MeshOp);
}

if (Node.Mesh1)
{
FMeshGenerationResult Mesh1Result;
GenerateMesh(Opts, Mesh1Result, Node.Mesh1);
mop->SetChild(mop->op.args.MeshMerge.added, Mesh1Result.MeshOp);
}

mop->op.args.MeshMerge.newSurfaceID = 0;
//mergeAd = mop;
OutResult.MeshOp = mop;
}`

Hey there,

I know that the MeshComponent nodes merge the meshes but it outputs a component and not the mesh itself so it isn’t very useful to us.

I would like to know your needs for taking this approach to outputting a mesh. Are you attempting to do this at runtime specifically, or do you need the skeletal mesh differently? While yes, the output is a component, the setup of Mutable is specifically for this type of operation, and I would not recommend duplicating that.

The MeshComponent node will create a skeletal mesh that is held in memory of the instance of the CustomizableMesh. That component ouput contains a skeletal mesh inside that is the output of the mesh merge, so you technically have access to that already. If you’re not looking for a runtime generation of that skeletal mesh, there is an offline baking option as well.

Hi Josh,

One thing that may be messing up the result is using ChildMeshData instead of the MeshData variable available in the context of the GenerateMutableSourceMesh function. MeshData gathers information from the meshes within a component to define the format of the output mesh. Using a dummy structure to generate the underlying meshes may cause the missing texture coordinates.

Could you try this small fix and tell me if it works?

Pere

Great.

You current issue is due to the newSurfaceID value set there. It cannot be 0 because it is a special case for the merge operation. A random uint32 > 0 will probably do the trick.

Instead of using a fixed number, try using a random or incremental number on each GenerateMesh_NSMeshMerge(…) call.

If that doesn’t work and you’re willing to send us a minimal reproduction and the modified plugin, we can take a look. Let me know if that’s the case, and we’ll make the question private.

Pere

Hi Dustin, thanks for the prompt reply.

We have another custom node that uses RBF to blend one shape (a gear piece) onto another shape (a body). This node requires mesh inputs, but the underlying functionality requires the body and head to be of a single unit in order for the collar of the gear pieces to properly be deformed.

Unfortunately, our setup has separate body meshes and head meshes, so we would need to merge that first and then feed it into the custom RBF node, but as a mesh, not a component. We are doing this entirely as an offline process through mutable graphs. I didn’t have much luck seeing how I could access the mesh on the component node, but maybe you can point me to it? That could potentially be a solve here.

Hopefully that helps clear it up, but I’d love to answer any futher questions!

Josh

Hi Pere, thanks for the reply.

I made the change you suggested and it does in fact stop crashing! Unfortunately it only seems to output whatever mesh is connected to the first pin. I have double checked all my mesh connections in code and it seems ok, so I think I’ll have to do some debugging to determine if I’ve done something wrong at a deeper level. I’ll update with some info tomorrow.

Thanks!

J

Hi Pere,

I set newSurfaceID to 1246987 but now it crashes in the following code:

`else if (pB && (pB->GetVertexCount() || pB->IsReference()))
{
Ptr Result = CloneOrTakeOver(pB);

THIS LINE —> check(Result->IsReference() || (Result->GetSurfaceCount() == 1) );

if (Result->GetSurfaceCount() > 0 && args.newSurfaceID)
{
Result->Surfaces.Last().Id = args.newSurfaceID;
}

Release¶;
StoreMesh(item, Result);
}`It appears that both pA and pB are null, but this is crashing before GenerateMesh_NSMeshMerge (I have unoptimized the file + added a breakpoint at the top of the function and it isn’t being hit) which is weird.

Unfortunately I can’t put a huge amount of time investigating because I’m in between projects, I’m just trying to close out this approach if possible before I fully swap to the new project.