Help with MultiMeshInstance3D set_instance_transform()

Godot Version

v4.7.2.stable.steam [ed1daf0bf]

Question

Hi! Relatively novice programmer here and new to the godot forums (hi!!). I have been trying to create a quick procedural voxel terrain visualiser to help me play with noise parameters in a sandbox separate from my main game.

I’m creating a 3d array of boolean block/air values and will be populating that with the noise function eventually. currently it just assigns a random value to each terrain_array[i][j][k] index.

The problem is with MultiMeshInstance3D. while I seem to be able to generate a number of instances equal to the number of true values in the 3d array, it seems that im unable to set their transforms individually with set_instance_transform(). As far as i can tell, every instance is moving to this position each time i update it. When i run my script, every cube ends up in the final index in 3d space.

The exact same code works fine when just spawning regular meshinstances as children at each array index converted to a coordinate. I don’t know what im doing wrong and i need help!!

extends Node3D

@export var block_mdl: Mesh
@export var mesh_material: Material
@export var cam: Node3D

var terrain_width: int = 32
var terrain_height: int = 16
var terrain_mesh: MultiMeshInstance3D = MultiMeshInstance3D.new()
var block_multimesh: MultiMesh = MultiMesh.new()
var terrain_array: Array
var stone_air: bool = true

func _ready() -> void:
	arraygen()
	meshgen()
	position_camera()

func arraygen():
	terrain_array.resize(terrain_width)
	for i in terrain_array.size():
		var z_array: Array
		z_array.resize(terrain_width)
		for j in z_array.size():
			var y_array: Array
			y_array.resize(terrain_height)
			for k in y_array.size():
				stone_air = randi_range(-1,0)
				y_array.set(k,stone_air)
			z_array.set(j,y_array)
		terrain_array.set(i,z_array)

##Meshing function, iterates through 3d noise array and creates an instance at every true index's 3d position
func meshgen():
	block_mdl.surface_set_material(0,mesh_material)
	block_multimesh.mesh = block_mdl
	block_multimesh.transform_format = MultiMesh.TRANSFORM_3D
	for i in terrain_width-1:
		for j in terrain_width-1:
			for k in terrain_height-1:
				if terrain_array[i][j][k] == true:
					block_multimesh.instance_count = block_multimesh.instance_count+1
					var instance_transform: Transform3D = Transform3D.IDENTITY.translated(Vector3(i,k,j))
					block_multimesh.set_instance_transform(block_multimesh.instance_count-1, instance_transform)
	terrain_mesh.set_multimesh(block_multimesh)
	add_child(terrain_mesh)

##Duplicate test function using regular mesh instances as nodes
func meshgen_slow():
	block_mdl.surface_set_material(0,mesh_material)
	for i in terrain_width-1:
		for j in terrain_width-1:
			for k in terrain_height-1:
				if terrain_array[i][j][k] == true:
					var block_inst: MeshInstance3D = MeshInstance3D.new()
					block_inst.mesh = block_mdl
					block_inst.position = Vector3(i,k,j)
					add_child(block_inst)

func position_camera():
	cam.position = Vector3(terrain_width/2,terrain_height+32,-terrain_width)
	cam.rotate(Vector3.UP,deg_to_rad(180))

the result of meshgen():

and the result of meshgen_slow():

Changing instance_count resets the buffers. This shouldn’t be incremented as you add instances. Instead, set it to final number of instances once at startup.

Thank you, I didnt know this! Is there anywhere I can learn about the way the buffer functions? the documentation on it is pretty light!

anyway here’s the revised function. Only downside is way more instances are made than needed to avoid having to go through the nested for loops an extra time:

func meshgen():
	block_mdl.surface_set_material(0,mesh_material)
	block_multimesh.mesh = block_mdl
	block_multimesh.transform_format = MultiMesh.TRANSFORM_3D
	var instance_count: int = terrain_width*terrain_width*terrain_height
	block_multimesh.instance_count = instance_count
	var instance_index: int
	for i in terrain_width-1:
		for j in terrain_width-1:
			for k in terrain_height-1:
				if terrain_array[i][j][k] == true:
					var instance_transform: Transform3D = Transform3D.IDENTITY.translated(Vector3(i,k,j))
					block_multimesh.set_instance_transform(instance_index, instance_transform)
					instance_index = instance_index+1
	terrain_mesh.set_multimesh(block_multimesh)
	add_child(terrain_mesh)

working here:

After you’re done populating transforms, you can set visible_instance_count to the value of last used index. This will limit the number of drawn instances to what you’re actually using.

thats really helpful thank you! I decided instead to just increment a counter every time a positive value passes through the noise function and use that to set the instance count :slight_smile: