Repository: godotengine/godot (language: cpp)

Issue:
GPUParticles3D collide with their gizmo icon, which can make them appear to not move at all if using the Point emission mode
### Tested versions

- Reproducible in: 4.4.beta1, 4.4.beta 7b1ed520b
- Not reproducible in: 4.3.stable

### System information

Godot v4.4.beta (7b1ed520b) - Fedora Linux 41 (KDE Plasma) on X11 - X11 display driver, Multi-window, 1 monitor - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 4090 (nvidia; 565.77) - 13th Gen Intel(R) Core(TM) i9-13900K (32 threads)

### Issue description

GPUParticles3D collide with their gizmo icon, which can make them appear to not move at all if using the Point emission mode.

I bisected the regression to https://github.com/godotengine/godot/pull/91174. This likely occurs because making the gizmo material use alpha scissor means it's now taken into account by the heightfield collision generator. We can probably fix this by changing the cull mask of the GPUParticlesCollisionHeightField3D to exclude the editor-only gizmo layers (`Node3DEditorViewport::GIZMO_EDIT_LAYER`).

This is further proven by the collision's shape matching the GPUParticles3D gizmo icon. You can see this by using a sphere emission shape and greatly increasing the particle count:

![Image](https://github.com/user-attachments/assets/c769a37c-a58b-408f-8c55-623bfe8e9c9e)

Relevant code:

https://github.com/godotengine/godot/blob/7b1ed520bdcca21d36e25e2094802b9b33dae2c6/editor/plugins/node_3d_editor_gizmos.cpp#L831-L837

https://github.com/godotengine/godot/blob/7b1ed520bdcca21d36e25e2094802b9b33dae2c6/scene/3d/gpu_particles_collision_3d.cpp

### Steps to reproduce

- Set up a scene with various meshes and particles that have collision enabled.
- Add a GPUParticlesCollisionHeightField3D node.
- Save the scene and use **Scene > Reload Saved Scene**.
- Notice how particles no longer move due to colliding with their gizmo.

### Minimal reproduction project (MRP)

[test_particle_collision_gizmo.zip](https://github.com/user-attachments/files/18464280/test_particle_collision_gizmo.zip)


Discussion / hints:
I've tried playing around with the VisualInstance3D layers property (which determines what the GPUParticlesCollisionHeightField3D takes into account when generating its collision shape). However, all collisions still occur as soon as `layers` is not equal to `0`. If it's equal to `0`, then all collision is disabled.

Changing `cull_mask` doesn't impact the collisions in any way (it's used to make different particles collide with different shapes instead; here, we want to exclude specific objects from the generated collision). Note that since `cull_mask` defaults to `0xFFFFFFFF` and only the first 20 layers are visible in the editor, you'll need to set `cull_mask = 0` in a `@tool` script to actually disable all layers (including the editor-specific layers that are otherwise hidden).
Also interesting to note is that this occurs with all nodes that inherit from Node3D and have an icon. 

Here it also collides with the icon of a AudioListener3D node.

![Image](https://github.com/user-attachments/assets/ca1cb227-190e-4468-86f8-307ab9cda26c)

Fix patch:
diff --git a/doc/classes/GPUParticlesCollisionHeightField3D.xml b/doc/classes/GPUParticlesCollisionHeightField3D.xml
index 0b9a94af6334..04bfbdb6fd56 100644
--- a/doc/classes/GPUParticlesCollisionHeightField3D.xml
+++ b/doc/classes/GPUParticlesCollisionHeightField3D.xml
@@ -12,11 +12,33 @@
 	</description>
 	<tutorials>
 	</tutorials>
+	<methods>
+		<method name="get_heightfield_mask_value" qualifiers="const">
+			<return type="bool" />
+			<param index="0" name="layer_number" type="int" />
+			<description>
+				Returns [code]true[/code] if the specified layer of the [member heightfield_mask] is enabled, given a [param layer_number] between [code]1[/code] and [code]20[/code], inclusive.
+			</description>
+		</method>
+		<method name="set_heightfield_mask_value">
+			<return type="void" />
+			<param index="0" name="layer_number" type="int" />
+			<param index="1" name="value" type="bool" />
+			<description>
+				Based on [param value], enables or disables the specified layer in the [member heightfield_mask], given a [param layer_number] between [code]1[/code] and [code]20[/code], inclusive.
+			</description>
+		</method>
+	</methods>
 	<members>
 		<member name="follow_camera_enabled" type="bool" setter="set_follow_camera_enabled" getter="is_follow_camera_enabled" default="false">
 			If [code]true[/code], the [GPUParticlesCollisionHeightField3D] will follow the current camera in global space. The [GPUParticlesCollisionHeightField3D] does not need to be a child of the [Camera3D] node for this to work.
 			Following the camera has a performance cost, as it will force the heightmap to update whenever the camera moves. Consider lowering [member resolution] to improve performance if [member follow_camera_enabled] is [code]true[/code].
 		</member>
+		<member name="heightfield_mask" type="int" setter="set_heightfield_mask" getter="get_heightfield_mask" default="1048575">
+			The visual layers to account for when updating the heightmap. Only [MeshInstance3D]s whose [member VisualInstance3D.layers] match with this [member heightfield_mask] will be included in the heightmap collision update. By default, all 20 user-visible layers are taken into account for updating the heightmap collision.
+			[b]Note:[/b] Since the [member heightfield_mask] allows for 32 layers to be stored in total, there are an additional 12 layers that are only used internally by the engine and aren't exposed in the editor. Setting [member heightfield_mask] using a script allows you to toggle those reserved layers, which can be useful for editor plugins.
+			To adjust [member heightfield_mask] more easily using a script, use [method get_heightfield_mask_value] and [method set_heightfield_mask_value].
+		</member>
 		<member name="resolution" type="int" setter="set_resolution" getter="get_resolution" enum="GPUParticlesCollisionHeightField3D.Resolution" default="2">
 			Higher resolutions can represent small details more accurately in large scenes, at the cost of lower performance. If [member update_mode] is [constant UPDATE_MODE_ALWAYS], consider using the lowest resolution possible.
 		</member>
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index fd16a665096c..f968c302f4c9 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -2893,6 +2893,14 @@
 				Sets the signed distance field [param texture] for the 3D GPU particles collision specified by the [param particles_collision] RID. Equivalent to [member GPUParticlesCollisionSDF3D.texture] or [member GPUParticlesAttractorVectorField3D.texture] depending on the [param particles_collision] type.
 			</description>
 		</method>
+		<method name="particles_collision_set_height_field_mask">
+			<return type="void" />
+			<param index="0" name="particles_collision" type="RID" />
+			<param index="1" name="mask" type="int" />
+			<description>
+				Sets the heightfield [param mask] for the 3D GPU particles heightfield collision specified by the [param particles_collision] RID. Equivalent to [member GPUParticlesCollisionHeightField3D.heightfield_mask].
+			</description>
+		</method>
 		<method name="particles_collision_set_height_field_resolution">
 			<return type="void" />
 			<param index="0" name="particles_collision" type="RID" />
diff --git a/drivers/gles3/storage/particles_storage.cpp b/drivers/gles3/storage/particles_storage.cpp
index 2ee8bad94476..73d7e340d2a8 100644
--- a/drivers/gles3/storage/particles_storage.cpp
+++ b/drivers/gles3/storage/particles_storage.cpp
@@ -1432,6 +1432,18 @@ bool ParticlesStorage::particles_collision_is_heightfield(RID p_particles_collis
 	return particles_collision->type == RS::PARTICLES_COLLISION_TYPE_HEIGHTFIELD_COLLIDE;
 }
 
+uint32_t ParticlesStorage::particles_collision_get_height_field_mask(RID p_particles_collision) const {
+	const ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_NULL_V(particles_collision, false);
+	return particles_collision->heightfield_mask;
+}
+
+void ParticlesStorage::particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_NULL(particles_collision);
+	particles_collision->heightfield_mask = p_heightfield_mask;
+}
+
 Dependency *ParticlesStorage::particles_collision_get_dependency(RID p_particles_collision) const {
 	ParticlesCollision *pc = particles_collision_owner.get_or_null(p_particles_collision);
 	ERR_FAIL_NULL_V(pc, nullptr);
diff --git a/drivers/gles3/storage/particles_storage.h b/drivers/gles3/storage/particles_storage.h
index 8d20d44935ed..382364abd6d2 100644
--- a/drivers/gles3/storage/particles_storage.h
+++ b/drivers/gles3/storage/particles_storage.h
@@ -287,6 +287,7 @@ class ParticlesStorage : public RendererParticlesStorage {
 		GLuint heightfield_texture = 0;
 		GLuint heightfield_fb = 0;
 		Size2i heightfield_fb_size;
+		uint32_t heightfield_mask = (1 << 20) - 1;
 
 		RS::ParticlesCollisionHeightfieldResolution heightfield_resolution = RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_1024;
 
@@ -434,6 +435,8 @@ class ParticlesStorage : public RendererParticlesStorage {
 	Vector3 particles_collision_get_extents(RID p_particles_collision) const;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override;
 	GLuint particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
+	virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override;
+	virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override;
 
 	_FORCE_INLINE_ Size2i particles_collision_get_heightfield_size(RID p_particles_collision) const {
 		ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
diff --git a/scene/3d/gpu_particles_collision_3d.cpp b/scene/3d/gpu_particles_collision_3d.cpp
index c6dc90836691..aa6f4650b2ac 100644
--- a/scene/3d/gpu_particles_collision_3d.cpp
+++ b/scene/3d/gpu_particles_collision_3d.cpp
@@ -721,6 +721,12 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_update_mode", "update_mode"), &GPUParticlesCollisionHeightField3D::set_update_mode);
 	ClassDB::bind_method(D_METHOD("get_update_mode"), &GPUParticlesCollisionHeightField3D::get_update_mode);
 
+	ClassDB::bind_method(D_METHOD("set_heightfield_mask", "heightfield_mask"), &GPUParticlesCollisionHeightField3D::set_heightfield_mask);
+	ClassDB::bind_method(D_METHOD("get_heightfield_mask"), &GPUParticlesCollisionHeightField3D::get_heightfield_mask);
+
+	ClassDB::bind_method(D_METHOD("set_heightfield_mask_value", "layer_number", "value"), &GPUParticlesCollisionHeightField3D::set_heightfield_mask_value);
+	ClassDB::bind_method(D_METHOD("get_heightfield_mask_value", "layer_number"), &GPUParticlesCollisionHeightField3D::get_heightfield_mask_value);
+
 	ClassDB::bind_method(D_METHOD("set_follow_camera_enabled", "enabled"), &GPUParticlesCollisionHeightField3D::set_follow_camera_enabled);
 	ClassDB::bind_method(D_METHOD("is_follow_camera_enabled"), &GPUParticlesCollisionHeightField3D::is_follow_camera_enabled);
 
@@ -728,6 +734,7 @@ void GPUParticlesCollisionHeightField3D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "resolution", PROPERTY_HINT_ENUM, "256 (Fastest),512 (Fast),1024 (Average),2048 (Slow),4096 (Slower),8192 (Slowest)"), "set_resolution", "get_resolution");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "update_mode", PROPERTY_HINT_ENUM, "When Moved (Fast),Always (Slow)"), "set_update_mode", "get_update_mode");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "follow_camera_enabled"), "set_follow_camera_enabled", "is_follow_camera_enabled");
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "heightfield_mask", PROPERTY_HINT_LAYERS_3D_RENDER), "set_heightfield_mask", "get_heightfield_mask");
 
 	BIND_ENUM_CONSTANT(RESOLUTION_256);
 	BIND_ENUM_CONSTANT(RESOLUTION_512);
@@ -790,6 +797,33 @@ GPUParticlesCollisionHeightField3D::UpdateMode GPUParticlesCollisionHeightField3
 	return update_mode;
 }
 
+void GPUParticlesCollisionHeightField3D::set_heightfield_mask(uint32_t p_heightfield_mask) {
+	heightfield_mask = p_heightfield_mask;
+	RS::get_singleton()->particles_collision_set_height_field_mask(_get_collision(), p_heightfield_mask);
+}
+
+uint32_t GPUParticlesCollisionHeightField3D::get_heightfield_mask() const {
+	return heightfield_mask;
+}
+
+void GPUParticlesCollisionHeightField3D::set_heightfield_mask_value(int p_layer_number, bool p_value) {
+	ERR_FAIL_COND_MSG(p_layer_number < 1, "Render layer number must be between 1 and 20 inclusive.");
+	ERR_FAIL_COND_MSG(p_layer_number > 20, "Render layer number must be between 1 and 20 inclusive.");
+	uint32_t mask = get_heightfield_mask();
+	if (p_value) {
+		mask |= 1 << (p_layer_number - 1);
+	} else {
+		mask &= ~(1 << (p_layer_number - 1));
+	}
+	set_heightfield_mask(mask);
+}
+
+bool GPUParticlesCollisionHeightField3D::get_heightfield_mask_value(int p_layer_number) const {
+	ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Render layer number must be between 1 and 20 inclusive.");
+	ERR_FAIL_COND_V_MSG(p_layer_number > 20, false, "Render layer number must be between 1 and 20 inclusive.");
+	return heightfield_mask & (1 << (p_layer_number - 1));
+}
+
 void GPUParticlesCollisionHeightField3D::set_follow_camera_enabled(bool p_enabled) {
 	follow_camera_mode = p_enabled;
 	set_process_internal(follow_camera_mode || update_mode == UPDATE_MODE_ALWAYS);
diff --git a/scene/3d/gpu_particles_collision_3d.h b/scene/3d/gpu_particles_collision_3d.h
index 16493200691f..3a21851ff9d2 100644
--- a/scene/3d/gpu_particles_collision_3d.h
+++ b/scene/3d/gpu_particles_collision_3d.h
@@ -225,6 +225,7 @@ class GPUParticlesCollisionHeightField3D : public GPUParticlesCollision3D {
 	};
 
 private:
+	uint32_t heightfield_mask = (1 << 20) - 1; // Only the first 20 bits are set by default to ignore editor layers.
 	Vector3 size = Vector3(2, 2, 2);
 	Resolution resolution = RESOLUTION_1024;
 	bool follow_camera_mode = false;
@@ -249,6 +250,12 @@ class GPUParticlesCollisionHeightField3D : public GPUParticlesCollision3D {
 	void set_update_mode(UpdateMode p_update_mode);
 	UpdateMode get_update_mode() const;
 
+	void set_heightfield_mask(uint32_t p_heightfield_mask);
+	uint32_t get_heightfield_mask() const;
+
+	void set_heightfield_mask_value(int p_layer_number, bool p_value);
+	bool get_heightfield_mask_value(int p_layer_number) const;
+
 	void set_follow_camera_enabled(bool p_enabled);
 	bool is_follow_camera_enabled() const;
 
diff --git a/servers/rendering/dummy/storage/particles_storage.h b/servers/rendering/dummy/storage/particles_storage.h
index 03160ea2d8ea..1b55b2afc79b 100644
--- a/servers/rendering/dummy/storage/particles_storage.h
+++ b/servers/rendering/dummy/storage/particles_storage.h
@@ -114,6 +114,8 @@ class ParticlesStorage : public RendererParticlesStorage {
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) override {}
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const override { return AABB(); }
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override { return false; }
+	virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override { return 0; }
+	virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override {}
 
 	virtual RID particles_collision_instance_create(RID p_collision) override { return RID(); }
 	virtual void particles_collision_instance_free(RID p_rid) override {}
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
index 6bb7eff83cf2..7b36e768b713 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.cpp
@@ -1857,6 +1857,18 @@ void ParticlesStorage::particles_collision_set_cull_mask(RID p_particles_collisi
 	particles_collision->cull_mask = p_cull_mask;
 }
 
+uint32_t ParticlesStorage::particles_collision_get_height_field_mask(RID p_particles_collision) const {
+	const ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_NULL_V(particles_collision, false);
+	return particles_collision->heightfield_mask;
+}
+
+void ParticlesStorage::particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) {
+	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
+	ERR_FAIL_NULL(particles_collision);
+	particles_collision->heightfield_mask = p_heightfield_mask;
+}
+
 void ParticlesStorage::particles_collision_set_sphere_radius(RID p_particles_collision, real_t p_radius) {
 	ParticlesCollision *particles_collision = particles_collision_owner.get_or_null(p_particles_collision);
 	ERR_FAIL_NULL(particles_collision);
diff --git a/servers/rendering/renderer_rd/storage_rd/particles_storage.h b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
index 27be125bfa28..9c46968f6402 100644
--- a/servers/rendering/renderer_rd/storage_rd/particles_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/particles_storage.h
@@ -404,6 +404,7 @@ class ParticlesStorage : public RendererParticlesStorage {
 		RID heightfield_texture;
 		RID heightfield_fb;
 		Size2i heightfield_fb_size;
+		uint32_t heightfield_mask = (1 << 20) - 1;
 
 		RS::ParticlesCollisionHeightfieldResolution heightfield_resolution = RS::PARTICLES_COLLISION_HEIGHTFIELD_RESOLUTION_1024;
 
@@ -581,6 +582,8 @@ class ParticlesStorage : public RendererParticlesStorage {
 	Vector3 particles_collision_get_extents(RID p_particles_collision) const;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const override;
 	RID particles_collision_get_heightfield_framebuffer(RID p_particles_collision) const;
+	virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const override;
+	virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) override;
 
 	Dependency *particles_collision_get_dependency(RID p_particles) const;
 
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index 0b98fc6769e7..fefe225e243a 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -3971,15 +3971,19 @@ void RendererSceneCull::render_particle_colliders() {
 
 			struct CullAABB {
 				PagedArray<Instance *> *result;
+				uint32_t heightfield_mask;
 				_FORCE_INLINE_ bool operator()(void *p_data) {
 					Instance *p_instance = (Instance *)p_data;
-					result->push_back(p_instance);
+					if (p_instance->layer_mask & heightfield_mask) {
+						result->push_back(p_instance);
+					}
 					return false;
 				}
 			};
 
 			CullAABB cull_aabb;
 			cull_aabb.result = &instance_cull_result;
+			cull_aabb.heightfield_mask = RSG::particles_storage->particles_collision_get_height_field_mask(hfpc->base);
 			hfpc->scenario->indexers[Scenario::INDEXER_GEOMETRY].aabb_query(hfpc->transformed_aabb, cull_aabb);
 			hfpc->scenario->indexers[Scenario::INDEXER_VOLUMES].aabb_query(hfpc->transformed_aabb, cull_aabb);
 
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 8e5faaf83816..ffbc5c709500 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -618,6 +618,7 @@ class RenderingServerDefault : public RenderingServer {
 	FUNC2(particles_collision_set_attractor_attenuation, RID, real_t)
 	FUNC2(particles_collision_set_field_texture, RID, RID)
 	FUNC1(particles_collision_height_field_update, RID)
+	FUNC2(particles_collision_set_height_field_mask, RID, uint32_t)
 	FUNC2(particles_collision_set_height_field_resolution, RID, ParticlesCollisionHeightfieldResolution)
 
 	/* FOG VOLUME */
diff --git a/servers/rendering/storage/particles_storage.h b/servers/rendering/storage/particles_storage.h
index 7543b05f5c5d..6208aceed5db 100644
--- a/servers/rendering/storage/particles_storage.h
+++ b/servers/rendering/storage/particles_storage.h
@@ -120,6 +120,8 @@ class RendererParticlesStorage {
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, RS::ParticlesCollisionHeightfieldResolution p_resolution) = 0; //for SDF and vector field
 	virtual AABB particles_collision_get_aabb(RID p_particles_collision) const = 0;
 	virtual bool particles_collision_is_heightfield(RID p_particles_collision) const = 0;
+	virtual uint32_t particles_collision_get_height_field_mask(RID p_particles_collision) const = 0;
+	virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) = 0;
 
 	//used from 2D and 3D
 	virtual RID particles_collision_instance_create(RID p_collision) = 0;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 422faa13a093..36229dc1dfd0 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2745,6 +2745,7 @@ void RenderingServer::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("particles_collision_height_field_update", "particles_collision"), &RenderingServer::particles_collision_height_field_update);
 	ClassDB::bind_method(D_METHOD("particles_collision_set_height_field_resolution", "particles_collision", "resolution"), &RenderingServer::particles_collision_set_height_field_resolution);
+	ClassDB::bind_method(D_METHOD("particles_collision_set_height_field_mask", "particles_collision", "mask"), &RenderingServer::particles_collision_set_height_field_mask);
 
 	BIND_ENUM_CONSTANT(PARTICLES_COLLISION_TYPE_SPHERE_ATTRACT);
 	BIND_ENUM_CONSTANT(PARTICLES_COLLISION_TYPE_BOX_ATTRACT);
diff --git a/servers/rendering_server.h b/servers/rendering_server.h
index 4880da439335..f889cce04820 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -857,6 +857,7 @@ class RenderingServer : public Object {
 	};
 
 	virtual void particles_collision_set_height_field_resolution(RID p_particles_collision, ParticlesCollisionHeightfieldResolution p_resolution) = 0; // For SDF and vector field.
+	virtual void particles_collision_set_height_field_mask(RID p_particles_collision, uint32_t p_heightfield_mask) = 0;
 
 	/* FOG VOLUME API */
 


Test patch: