SurfaceTool generated mesh not visible if using SetBones/SetWeights

Godot Version

4.1.1
4.2.1

Question

I’ve been trying to programmatically generate a 3d mesh, with bones attached to the vertices. Everything seems to be working to create the mesh. However once I use set_bones at all the rendered mesh ceases to be visible in the scene. If I then go into the Remote scene tree in the editor, right click and save the existing node as a scene, the mesh and bones are linked up properly in the saved scene and the mesh is visible and deformable. This leads me to think I’m using the APIs something akin to correctly, but I’ve run out of my ability to figure out what might be the cause.

The scene tree consists of the following:
image

The script is attached below. In the code the issue shows up if the add_bones flag is set to true.

using Godot;

public partial class GenerateMeshTest : Skeleton3D {
	[Export]
	public Material Mat { get; set; }

	[Export]
	public MeshInstance3D MeshInstance { get; set; }

	[Export]
	public Camera3D Camera { get; set; }

	private int max_x = 4;
	private int max_y = 5;
	private bool add_bones = true;

	public override void _Ready() {
		using SurfaceTool surface_tool = new SurfaceTool();

		ArrayMesh mesh;

		surface_tool.Begin(Mesh.PrimitiveType.Triangles);
		surface_tool.SetMaterial(Mat);

		surface_tool.SetColor(Colors.Red);

		AddBone($"b0");
		AddBone($"b1");
		AddBone($"b3");
		AddBone($"b4");

		SetBoneParent(1, 0);
		SetBoneParent(2, 1);
		SetBoneParent(3, 2);

		SetBoneEnabled(0, true);
		SetBoneEnabled(1, true);
		SetBoneEnabled(2, true);
		SetBoneEnabled(3, true);

		SetBoneRest(0, new Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0));
		SetBoneRest(1, new Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0));
		SetBoneRest(2, new Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0));
		SetBoneRest(3, new Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 2, 0));

		SetBonePosePosition(0, new Vector3(0, 0, 0));
		SetBonePosePosition(1, new Vector3(0, 2, 0));
		SetBonePosePosition(2, new Vector3(0, 2, 0));
		SetBonePosePosition(3, new Vector3(0, 2, 0));

		for (int y = 0; y < max_y; y++) {
			for (int x = 0; x < max_x; x++) {
				AddQuad(surface_tool,
					new Vector3(x + 0, y + 0, 0),
					new Vector3(x + 0, y + 1, 0),
					new Vector3(x + 1, y + 1, 0),
					new Vector3(x + 1, y + 0, 0));

			}
		}

		surface_tool.GenerateNormals();
		surface_tool.GenerateTangents();
		surface_tool.Index();

		// mesh = surface_tool.Commit();
		mesh = (ArrayMesh)this.MeshInstance.Mesh;
		surface_tool.Commit(mesh);

		MeshInstance.Mesh = mesh;

		//CreateSkinFromRestTransforms();
	}

	private double time = 0;
	public override void _Process(double delta) {
		time += delta;

		//SetBonePoseRotation(1, Quaternion.FromEuler(new Vector3(0, (float)time, 0)));

		Camera.GlobalPosition = MeshInstance.GlobalPosition + new Vector3(0, 0, 10 + (float)time);
		Camera.LookAt(MeshInstance.GlobalPosition);
	}

	private void AddQuad(SurfaceTool surface_tool, Vector3 vertex0, Vector3 vertex1, Vector3 vertex2, Vector3 vertex3) {
		// Triangle 1
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex0.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(0, 0));
		surface_tool.AddVertex(vertex0);
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex1.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(1, 0));
		surface_tool.AddVertex(vertex1);
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex2.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(1, 1));
		surface_tool.AddVertex(vertex2);

		// Triangle 2
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex0.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(0, 0));
		surface_tool.AddVertex(vertex0);
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex2.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(1, 1));
		surface_tool.AddVertex(vertex2);
		if (add_bones) {
			surface_tool.SetBones(new int[] { (int)(vertex3.Y / 2), 0, 0, 0 });
			surface_tool.SetWeights(new float[] { 1, 0, 0, 0 });
		}
		surface_tool.SetUV(new Vector2(0, 1));
		surface_tool.AddVertex(vertex3);
	}
}

I’ve tinkered more on this, and dug through the engine source a bit (unfortunately it didn’t help, but I learned some other unrelated stuff :metal:). Altering the code in the following manner is providing consistent results for me.

Previously I was creating a MeshInstance3D node in the editor, setting it up with a new ArrayMesh in its Mesh property, then committing the SurfaceTool to the existing mesh.

Instead, I do not have that node in the editor, I create a new MeshInstance3D and set its Mesh property to the output of surface_tool.Commit() and then AddChild to the Skeleton3D.

The results are now consistent, best guess is some state is not transferred in the path I was walking before. Unfortunately I have run out of hours and motivation to track down the specific cause, but I hope this proves insightful for anyone else.

K

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.