Why is setting radius taking so long time?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By toblin

Background

I’m optimizing using servers, and have a custom object called Cell that has objects in PhysicsServer and VisualServer attached to it. The Cell has an instance in the VisualServer that has a SphereMesh (documentation) as its mesh, and the body and areas have SphereShapes (documentation) as their shapes. A summary of the definition in the Cell class:

# VISUAL
visualinstance           = VisualServer.instance_create()
visualmesh               = SphereMesh.new()
VisualServer.instance_set_base(visualinstance, visualmesh)

# AREAs
contact_area             = PhysicsServer.area_create()
contact_area_shape	     = SphereShape.new()
PhysicsServer.area_add_shape( contact_area, contact_area_shape, ... )
transmission_area	     = PhysicsServer.area_create()
transmission_area_shape  = SphereShape.new()
PhysicsServer.area_add_shape( transmission_area, transmission_area_shape, ... )

# PHYSICS
body                     = PhysicsServer.body_create()
body_shape 				 = SphereShape.new()
PhysicsServer.body_add_shape(body, body_shape)

where ( , …) means that there is additional code that I’m omitting since I don’t think it relevant to the question.

Question

Now to the question. When running, I have a function that sets the radius of the Cell, for example as it grows or when it divides, that I’ve defined either like this:

func set_cell_radius_example_1(new_body_radius:float):
	body_shape.radius = new_body_radius
	visualmesh.radius = new_body_radius
	visualmesh.height = new_body_radius*2
	contact_area_shape.radius = new_body_radius*3
	transmission_area_shape.radius = new_body_radius*4

or like this:

func set_cell_radius_example_2(new_body_radius:float):
    body_shape.set_radius(new_body_radius)
    visualmesh.set_radius(new_body_radius)
    visualmesh.set_height(new_body_radius*2)
    contact_area_shape.set_radius(new_body_radius*3)
    transmission_area_shape.set_radius(new_body_radius*4)

For both definitions of set_cell_radius, they seem to be taking a surprisingly long time to execute when analyzed using the debugger profiler. Here’s an screenshot of the Profiler output:

enter image description here

that shows that the set_cell_radius functions take on average 4,5 milliseconds to execute. This seems very high to me since it simply sets five parameters.

Why is this the case? Are my intuitions wrong and this is “normal”? Or am I doing something wrong…?

:bust_in_silhouette: Reply From: Zylann

About graphics:

When you change the radius of a SphereMesh (I assume it’s a SphereMesh, not a MeshInstance), the engine has to recompute the entire mesh that composes it, which is actually made of TONS of vertices. If you do this every frame with a hundred of cells, that’s going to put pressure on the CPU and hammer the graphics card with hundreds of new meshes to upload every frame.
Worse, I guess you made that mesh unique so that each cell can grow independently. This is very bad, because it’s copying the mesh for every cell, while they might be able to share the same one. (it also prevents from using GPU instancing later on, which is one of the ways to optimize this further).

I would recommend you just scale the sphere MeshInstance and re-use the same sphere mesh, instead of regenerating it individually. That will be a lot cheaper.

About physics:

I’m not a expert in physics engine internals, but my assumption is that the physics engine works best when the shape of objects remain constant. It is usually quite rare to see an object change shape in a game involving physics.
If the shape changes size, it means the physics engine has to recompute how that shape sits within the physics world, entirely. That means recomputing its location in the tree data structures, checking if it overlaps with anything else, and other things which I’m not aware of.

While changing the collision sphere is probably less costly than the graphics, I would recommend you don’t change it every frame, but maybe once per second, if it’s slow enough.

Note: optimizing with servers still has nothing to do with this :wink:

Awesome! Makes sense =). Thanks for explaining! And yes, it’s a SphereMesh I’m using.

I would recommend you just scale the sphere MeshInstance and re-use the same sphere mesh, instead of regenerating it individually. That will be a lot cheaper.

How would I go about doing that? Should I create one mesh instance in the meta-code (outside of the Cell class) such as visualmesh = SphereMesh.new(), and then pass it to each Cell instance? I don’t understand why then scaling the mesh in each Cell would not change the scale on the only mesh instance that exists (visualmesh).

I’m also having troubles finding a scale() for the SphereMesh in the documentation for it and all the classes it inherits. How do I scale the SphereMesh?

Response note: good to know it has nothing to do with the servers =). I mentioned it in case it had something to do with using the servers directly. I was guessing that the problem was that the mesh and shapes should be spawned in the servers somehow (e.g. using PhysicsServer.shape_create()).

toblin | 2020-04-29 07:14

Ok, I so a MeshInstance can be scaled, but not a SphereMesh. Since I’ll be dealing with thousands of instances, I’m wondering if it’s not better to use a MultiMesh? My requirement is however that each mesh instance be individually scalable. I’ve asked the question of how to do so using a MultiMesh, and whether or not it is possible, here.

toblin | 2020-04-29 08:35

Ok, so I think I answered my own question of how to scale the mesh instances of a MultiMesh. I did so here.

toblin | 2020-04-29 10:12

Should I create one mesh instance in the meta-code (outside of the Cell class) such as visualmesh = SphereMesh.new(), and then pass it to each Cell instance?

Yeah, I guess. Or just load it from a resource and assign it. That mesh should never need to change, really. Unless you want some of your cells to become cubes.

I’m also having troubles finding a scale() for the SphereMesh in the documentation for it and all the classes it inherits. How do I scale the SphereMesh?

a MeshInstance can be scaled, but not a SphereMesh

Really, don’t scale the SphereMesh, that’s the whole point in my answer. SphereMesh is just the data that composes the sphere, the drawn size should be decided only using the scale of the MeshInstance or MultiMeshInstance transforms. Scale properties on the instances is what makes this cheaper because it will happen on the GPU.

I don’t understand why then scaling the mesh in each Cell would not change the scale on the only mesh instance that exists (visualmesh).

I don’t understand what you describe. Just give each cell a MeshInstance using the same SphereMesh, and scale the instances. Or use a multimesh. Also make sure the visual node is not the root of the cell, because the physic node should not be scaled. It’s a matter of picking different strategies for visuals and physics.

Zylann | 2020-04-29 17:18

@Zylann, I appreciate your persistence in this question =). This is what I’ve done: I’ve used a MultiMesh with a SphereMesh as its mesh:

extends Spatial

func _ready():
    multimeshinstance = MultiMeshInstance.new()
    multimeshinstance.multimesh = MultiMesh.new()
    add_child(multimeshinstance)
    multimeshinstance.multimesh.transform_format = MultiMesh.TRANSFORM_3D
    multimeshinstance.multimesh.mesh = SphereMesh.new()
    multimeshinstance.multimesh.mesh.radial_segments = 8
    multimeshinstance.multimesh.mesh.rings = 8
    multimeshinstance.multimesh.instance_count = MULTIMESH_NUMBER_OF_INSTANCES
    multimeshinstance.multimesh.visible_instance_count = 0

Then to change the size of the cells, I scale the transform of the multimesh instances:

func set_visualmesh_radius( new_radius ):
	visualmesh_radius = new_radius
	var visualmesh_transform = multimesh.get_instance_transform( visualmesh_idx )
	visualmesh_transform.basis.x = visualmesh_transform.basis.x.normalized()*new_radius
	visualmesh_transform.basis.y = visualmesh_transform.basis.y.normalized()*new_radius
	visualmesh_transform.basis.z = visualmesh_transform.basis.z.normalized()*new_radius
	multimesh.set_instance_transform( visualmesh_idx, visualmesh_transform)

Doing this, I have seen drastic increases in performance. The setting of radii has gone from taking 10s of seconds to 0.1s of seconds when the ball count goes up high.

Is this method equivalent to the scaling of a MeshInstance instead of scaling the SphereMesh as you suggest? I.e. is it efficient?

Also make sure the visual node is not the root of the cell, because the physic node should not be scaled. It’s a matter of picking different strategies for visuals and physics.

This makes sense, but I don’t think there’s much risk in my case. Spawning everything through the servers means that there is no node tree to worry about (except for the MultiMesh which I’ve added as a child to the Spatial node).

One downside to this method is that I don’t understand how to make instances of the MultiMesh invisible when the cells die. I’ve asked that question here for anyone that’s interested.

toblin | 2020-04-30 06:53

Scaling transforms on a MultiMeshInstance is probably the fastest alternative yeah. Only downside being, One cell is no longer contained within a single scene, visuals are shared within a MultiMeshInstance. Btw, you may want to have a look at this video: https://www.youtube.com/watch?v=pmyOYInhlkI

Zylann | 2020-04-30 12:08