Browse Source

Merge pull request #49834 from nekomatata/physics-disable-modes

Add support for controlling physics nodes' behavior when disabled
Rémi Verschelde 4 years ago
parent
commit
915344fe76

+ 14 - 0
doc/classes/CollisionObject2D.xml

@@ -266,6 +266,9 @@
 			The physics layers this CollisionObject2D scans. Collision objects can scan one or more of 32 different layers. See also [member collision_layer].
 			[b]Note:[/b] A contact is detected if object A is in any of the layers that object B scans, or object B is in any layers that object A scans. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
 		</member>
+		<member name="disable_mode" type="int" setter="set_disable_mode" getter="get_disable_mode" enum="CollisionObject2D.DisableMode" default="0">
+			Defines the behavior in physics when [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED]. See [enum DisableMode] for more details about the different modes.
+		</member>
 		<member name="input_pickable" type="bool" setter="set_pickable" getter="is_pickable" default="true">
 			If [code]true[/code], this object is pickable. A pickable object can detect the mouse pointer entering/leaving, and if the mouse is inside it, report input events. Requires at least one [code]collision_layer[/code] bit to be set.
 		</member>
@@ -294,5 +297,16 @@
 		</signal>
 	</signals>
 	<constants>
+		<constant name="DISABLE_MODE_REMOVE" value="0" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], remove from the physics simulation to stop all physics interactions with this [CollisionObject2D].
+			Automatically re-added to the physics simulation when the [Node] is processed again.
+		</constant>
+		<constant name="DISABLE_MODE_MAKE_STATIC" value="1" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], make the body static. Doesn't affect [Area2D]. [PhysicsBody2D] can't be affected by forces or other bodies while static.
+			Automatically set [PhysicsBody2D] back to its original mode when the [Node] is processed again.
+		</constant>
+		<constant name="DISABLE_MODE_KEEP_ACTIVE" value="2" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], do not affect the physics simulation.
+		</constant>
 	</constants>
 </class>

+ 14 - 0
doc/classes/CollisionObject3D.xml

@@ -230,6 +230,9 @@
 			The physics layers this CollisionObject3D scans. Collision objects can scan one or more of 32 different layers. See also [member collision_layer].
 			[b]Note:[/b] A contact is detected if object A is in any of the layers that object B scans, or object B is in any layers that object A scans. See [url=https://docs.godotengine.org/en/latest/tutorials/physics/physics_introduction.html#collision-layers-and-masks]Collision layers and masks[/url] in the documentation for more information.
 		</member>
+		<member name="disable_mode" type="int" setter="set_disable_mode" getter="get_disable_mode" enum="CollisionObject3D.DisableMode" default="0">
+			Defines the behavior in physics when [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED]. See [enum DisableMode] for more details about the different modes.
+		</member>
 		<member name="input_capture_on_drag" type="bool" setter="set_capture_input_on_drag" getter="get_capture_input_on_drag" default="false">
 			If [code]true[/code], the [CollisionObject3D] will continue to receive input events as the mouse is dragged across its shapes.
 		</member>
@@ -265,5 +268,16 @@
 		</signal>
 	</signals>
 	<constants>
+		<constant name="DISABLE_MODE_REMOVE" value="0" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], remove from the physics simulation to stop all physics interactions with this [CollisionObject3D].
+			Automatically re-added to the physics simulation when the [Node] is processed again.
+		</constant>
+		<constant name="DISABLE_MODE_MAKE_STATIC" value="1" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], make the body static. Doesn't affect [Area2D]. [PhysicsBody3D] can't be affected by forces or other bodies while static.
+			Automatically set [PhysicsBody3D] back to its original mode when the [Node] is processed again.
+		</constant>
+		<constant name="DISABLE_MODE_KEEP_ACTIVE" value="2" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], do not affect the physics simulation.
+		</constant>
 	</constants>
 </class>

+ 6 - 0
doc/classes/Node.xml

@@ -904,6 +904,12 @@
 		<constant name="NOTIFICATION_POST_ENTER_TREE" value="27">
 			Notification received when the node is ready, just before [constant NOTIFICATION_READY] is received. Unlike the latter, it's sent every time the node enters tree, instead of only once.
 		</constant>
+		<constant name="NOTIFICATION_DISABLED" value="28">
+			Notification received when the node is disabled. See [constant PROCESS_MODE_DISABLED].
+		</constant>
+		<constant name="NOTIFICATION_ENABLED" value="29">
+			Notification received when the node is enabled again after being disabled. See [constant PROCESS_MODE_DISABLED].
+		</constant>
 		<constant name="NOTIFICATION_EDITOR_PRE_SAVE" value="9001">
 			Notification received right before the scene with the node is saved in the editor. This notification is only sent in the Godot editor and will not occur in exported projects.
 		</constant>

+ 10 - 0
doc/classes/SoftBody3D.xml

@@ -93,6 +93,9 @@
 		</member>
 		<member name="damping_coefficient" type="float" setter="set_damping_coefficient" getter="get_damping_coefficient" default="0.01">
 		</member>
+		<member name="disable_mode" type="int" setter="set_disable_mode" getter="get_disable_mode" enum="SoftBody3D.DisableMode" default="0">
+			Defines the behavior in physics when [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED]. See [enum DisableMode] for more details about the different modes.
+		</member>
 		<member name="drag_coefficient" type="float" setter="set_drag_coefficient" getter="get_drag_coefficient" default="0.0">
 		</member>
 		<member name="linear_stiffness" type="float" setter="set_linear_stiffness" getter="get_linear_stiffness" default="0.5">
@@ -113,5 +116,12 @@
 		</member>
 	</members>
 	<constants>
+		<constant name="DISABLE_MODE_REMOVE" value="0" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], remove from the physics simulation to stop all physics interactions with this [SoftBody3D].
+			Automatically re-added to the physics simulation when the [Node] is processed again.
+		</constant>
+		<constant name="DISABLE_MODE_KEEP_ACTIVE" value="1" enum="DisableMode">
+			When [member Node.process_mode] is set to [constant Node.PROCESS_MODE_DISABLED], do not affect the physics simulation.
+		</constant>
 	</constants>
 </class>

+ 132 - 13
scene/2d/collision_object_2d.cpp

@@ -31,7 +31,6 @@
 #include "collision_object_2d.h"
 
 #include "scene/scene_string_names.h"
-#include "servers/physics_server_2d.h"
 
 void CollisionObject2D::_notification(int p_what) {
 	switch (p_what) {
@@ -44,16 +43,22 @@ void CollisionObject2D::_notification(int p_what) {
 				PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform);
 			}
 
-			RID space = get_world_2d()->get_space();
-			if (area) {
-				PhysicsServer2D::get_singleton()->area_set_space(rid, space);
-			} else {
-				PhysicsServer2D::get_singleton()->body_set_space(rid, space);
+			bool disabled = !is_enabled();
+
+			if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
+				_apply_disabled();
 			}
 
-			_update_pickable();
+			if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
+				RID space = get_world_2d()->get_space();
+				if (area) {
+					PhysicsServer2D::get_singleton()->area_set_space(rid, space);
+				} else {
+					PhysicsServer2D::get_singleton()->body_set_space(rid, space);
+				}
+			}
 
-			//get space
+			_update_pickable();
 		} break;
 
 		case NOTIFICATION_ENTER_CANVAS: {
@@ -67,6 +72,7 @@ void CollisionObject2D::_notification(int p_what) {
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			_update_pickable();
 		} break;
+
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 			if (only_update_transform_changes) {
 				return;
@@ -79,15 +85,22 @@ void CollisionObject2D::_notification(int p_what) {
 			} else {
 				PhysicsServer2D::get_singleton()->body_set_state(rid, PhysicsServer2D::BODY_STATE_TRANSFORM, global_transform);
 			}
-
 		} break;
+
 		case NOTIFICATION_EXIT_TREE: {
-			if (area) {
-				PhysicsServer2D::get_singleton()->area_set_space(rid, RID());
-			} else {
-				PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
+			bool disabled = !is_enabled();
+
+			if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
+				if (area) {
+					PhysicsServer2D::get_singleton()->area_set_space(rid, RID());
+				} else {
+					PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
+				}
 			}
 
+			if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
+				_apply_enabled();
+			}
 		} break;
 
 		case NOTIFICATION_EXIT_CANVAS: {
@@ -97,6 +110,14 @@ void CollisionObject2D::_notification(int p_what) {
 				PhysicsServer2D::get_singleton()->body_attach_canvas_instance_id(rid, ObjectID());
 			}
 		} break;
+
+		case NOTIFICATION_DISABLED: {
+			_apply_disabled();
+		} break;
+
+		case NOTIFICATION_ENABLED: {
+			_apply_enabled();
+		} break;
 	}
 }
 
@@ -158,6 +179,79 @@ bool CollisionObject2D::get_collision_mask_bit(int p_bit) const {
 	return get_collision_mask() & (1 << p_bit);
 }
 
+void CollisionObject2D::set_disable_mode(DisableMode p_mode) {
+	if (disable_mode == p_mode) {
+		return;
+	}
+
+	bool disabled = is_inside_tree() && !is_enabled();
+
+	if (disabled) {
+		// Cancel previous disable mode.
+		_apply_enabled();
+	}
+
+	disable_mode = p_mode;
+
+	if (disabled) {
+		// Apply new disable mode.
+		_apply_disabled();
+	}
+}
+
+CollisionObject2D::DisableMode CollisionObject2D::get_disable_mode() const {
+	return disable_mode;
+}
+
+void CollisionObject2D::_apply_disabled() {
+	switch (disable_mode) {
+		case DISABLE_MODE_REMOVE: {
+			if (is_inside_tree()) {
+				if (area) {
+					PhysicsServer2D::get_singleton()->area_set_space(rid, RID());
+				} else {
+					PhysicsServer2D::get_singleton()->body_set_space(rid, RID());
+				}
+			}
+		} break;
+
+		case DISABLE_MODE_MAKE_STATIC: {
+			if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) {
+				PhysicsServer2D::get_singleton()->body_set_mode(rid, PhysicsServer2D::BODY_MODE_STATIC);
+			}
+		} break;
+
+		case DISABLE_MODE_KEEP_ACTIVE: {
+			// Nothing to do.
+		} break;
+	}
+}
+
+void CollisionObject2D::_apply_enabled() {
+	switch (disable_mode) {
+		case DISABLE_MODE_REMOVE: {
+			if (is_inside_tree()) {
+				RID space = get_world_2d()->get_space();
+				if (area) {
+					PhysicsServer2D::get_singleton()->area_set_space(rid, space);
+				} else {
+					PhysicsServer2D::get_singleton()->body_set_space(rid, space);
+				}
+			}
+		} break;
+
+		case DISABLE_MODE_MAKE_STATIC: {
+			if (!area && (body_mode != PhysicsServer2D::BODY_MODE_STATIC)) {
+				PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode);
+			}
+		} break;
+
+		case DISABLE_MODE_KEEP_ACTIVE: {
+			// Nothing to do.
+		} break;
+	}
+}
+
 uint32_t CollisionObject2D::create_shape_owner(Object *p_owner) {
 	ShapeData sd;
 	uint32_t id;
@@ -412,6 +506,22 @@ bool CollisionObject2D::is_only_update_transform_changes_enabled() const {
 	return only_update_transform_changes;
 }
 
+void CollisionObject2D::set_body_mode(PhysicsServer2D::BodyMode p_mode) {
+	ERR_FAIL_COND(area);
+
+	if (body_mode == p_mode) {
+		return;
+	}
+
+	body_mode = p_mode;
+
+	if (is_inside_tree() && !is_enabled() && (disable_mode == DISABLE_MODE_MAKE_STATIC)) {
+		return;
+	}
+
+	PhysicsServer2D::get_singleton()->body_set_mode(rid, p_mode);
+}
+
 void CollisionObject2D::_update_pickable() {
 	if (!is_inside_tree()) {
 		return;
@@ -445,6 +555,8 @@ void CollisionObject2D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &CollisionObject2D::get_collision_layer_bit);
 	ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &CollisionObject2D::set_collision_mask_bit);
 	ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &CollisionObject2D::get_collision_mask_bit);
+	ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject2D::set_disable_mode);
+	ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject2D::get_disable_mode);
 	ClassDB::bind_method(D_METHOD("set_pickable", "enabled"), &CollisionObject2D::set_pickable);
 	ClassDB::bind_method(D_METHOD("is_pickable"), &CollisionObject2D::is_pickable);
 	ClassDB::bind_method(D_METHOD("create_shape_owner", "owner"), &CollisionObject2D::create_shape_owner);
@@ -473,12 +585,18 @@ void CollisionObject2D::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("mouse_entered"));
 	ADD_SIGNAL(MethodInfo("mouse_exited"));
 
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,MakeStatic,KeepActive"), "set_disable_mode", "get_disable_mode");
+
 	ADD_GROUP("Collision", "collision_");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_layer", "get_collision_layer");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_2D_PHYSICS), "set_collision_mask", "get_collision_mask");
 
 	ADD_GROUP("Input", "input_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_pickable"), "set_pickable", "is_pickable");
+
+	BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE);
+	BIND_ENUM_CONSTANT(DISABLE_MODE_MAKE_STATIC);
+	BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE);
 }
 
 CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) {
@@ -493,6 +611,7 @@ CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) {
 		PhysicsServer2D::get_singleton()->area_attach_object_instance_id(rid, get_instance_id());
 	} else {
 		PhysicsServer2D::get_singleton()->body_attach_object_instance_id(rid, get_instance_id());
+		PhysicsServer2D::get_singleton()->body_set_mode(rid, body_mode);
 	}
 }
 

+ 23 - 0
scene/2d/collision_object_2d.h

@@ -33,10 +33,19 @@
 
 #include "scene/2d/node_2d.h"
 #include "scene/resources/shape_2d.h"
+#include "servers/physics_server_2d.h"
 
 class CollisionObject2D : public Node2D {
 	GDCLASS(CollisionObject2D, Node2D);
 
+public:
+	enum DisableMode {
+		DISABLE_MODE_REMOVE,
+		DISABLE_MODE_MAKE_STATIC,
+		DISABLE_MODE_KEEP_ACTIVE,
+	};
+
+private:
 	uint32_t collision_layer = 1;
 	uint32_t collision_mask = 1;
 
@@ -44,6 +53,10 @@ class CollisionObject2D : public Node2D {
 	RID rid;
 	bool pickable = false;
 
+	DisableMode disable_mode = DISABLE_MODE_REMOVE;
+
+	PhysicsServer2D::BodyMode body_mode = PhysicsServer2D::BODY_MODE_STATIC;
+
 	struct ShapeData {
 		Object *owner = nullptr;
 		Transform2D xform;
@@ -64,6 +77,9 @@ class CollisionObject2D : public Node2D {
 	Map<uint32_t, ShapeData> shapes;
 	bool only_update_transform_changes = false; //this is used for sync physics in CharacterBody2D
 
+	void _apply_disabled();
+	void _apply_enabled();
+
 protected:
 	CollisionObject2D(RID p_rid, bool p_area);
 
@@ -79,6 +95,8 @@ protected:
 	void set_only_update_transform_changes(bool p_enable);
 	bool is_only_update_transform_changes_enabled() const;
 
+	void set_body_mode(PhysicsServer2D::BodyMode p_mode);
+
 public:
 	void set_collision_layer(uint32_t p_layer);
 	uint32_t get_collision_layer() const;
@@ -92,6 +110,9 @@ public:
 	void set_collision_mask_bit(int p_bit, bool p_value);
 	bool get_collision_mask_bit(int p_bit) const;
 
+	void set_disable_mode(DisableMode p_mode);
+	DisableMode get_disable_mode() const;
+
 	uint32_t create_shape_owner(Object *p_owner);
 	void remove_shape_owner(uint32_t owner);
 	void get_shape_owners(List<uint32_t> *r_owners);
@@ -131,4 +152,6 @@ public:
 	~CollisionObject2D();
 };
 
+VARIANT_ENUM_CAST(CollisionObject2D::DisableMode);
+
 #endif // COLLISION_OBJECT_2D_H

+ 33 - 29
scene/2d/physical_bone_2d.cpp

@@ -31,33 +31,37 @@
 #include "physical_bone_2d.h"
 
 void PhysicalBone2D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
-		// Position the RigidBody in the correct position
-		if (follow_bone_when_simulating) {
-			_position_at_bone2d();
-		}
+	switch (p_what) {
+		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+			// Position the RigidBody in the correct position.
+			if (follow_bone_when_simulating) {
+				_position_at_bone2d();
+			}
 
-		// Keep the child joint in the correct position.
-		if (child_joint && auto_configure_joint) {
-			child_joint->set_global_position(get_global_position());
-		}
-	} else if (p_what == NOTIFICATION_READY) {
-		_find_skeleton_parent();
-		_find_joint_child();
+			// Keep the child joint in the correct position.
+			if (child_joint && auto_configure_joint) {
+				child_joint->set_global_position(get_global_position());
+			}
+		} break;
 
-		// Configure joint
-		if (child_joint && auto_configure_joint) {
-			_auto_configure_joint();
-		}
+		case NOTIFICATION_READY: {
+			_find_skeleton_parent();
+			_find_joint_child();
 
-		// Simulate physics if set
-		if (simulate_physics) {
-			_start_physics_simulation();
-		} else {
-			_stop_physics_simulation();
-		}
+			// Configure joint.
+			if (child_joint && auto_configure_joint) {
+				_auto_configure_joint();
+			}
+
+			// Simulate physics if set.
+			if (simulate_physics) {
+				_start_physics_simulation();
+			} else {
+				_stop_physics_simulation();
+			}
 
-		set_physics_process_internal(true);
+			set_physics_process_internal(true);
+		} break;
 	}
 }
 
@@ -156,16 +160,16 @@ void PhysicalBone2D::_start_physics_simulation() {
 	// Apply the correct mode
 	RigidBody2D::Mode rigid_mode = get_mode();
 	if (rigid_mode == RigidBody2D::MODE_STATIC) {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_STATIC);
+		set_body_mode(PhysicsServer2D::BODY_MODE_STATIC);
 	} else if (rigid_mode == RigidBody2D::MODE_DYNAMIC) {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC);
+		set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC);
 	} else if (rigid_mode == RigidBody2D::MODE_KINEMATIC) {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_KINEMATIC);
+		set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC);
 	} else if (rigid_mode == RigidBody2D::MODE_DYNAMIC_LOCKED) {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC_LOCKED);
+		set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED);
 	} else {
-		// Default to Rigid
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BodyMode::BODY_MODE_DYNAMIC);
+		// Default to Dynamic.
+		set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC);
 	}
 
 	_internal_simulate_physics = true;

+ 55 - 50
scene/2d/physics_body_2d.cpp

@@ -49,7 +49,7 @@ void PhysicsBody2D::_bind_methods() {
 
 PhysicsBody2D::PhysicsBody2D(PhysicsServer2D::BodyMode p_mode) :
 		CollisionObject2D(PhysicsServer2D::get_singleton()->body_create(), false) {
-	PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), p_mode);
+	set_body_mode(p_mode);
 	set_pickable(false);
 }
 
@@ -219,9 +219,9 @@ void StaticBody2D::set_kinematic_motion_enabled(bool p_enabled) {
 	kinematic_motion = p_enabled;
 
 	if (kinematic_motion) {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_KINEMATIC);
+		set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC);
 	} else {
-		PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_STATIC);
+		set_body_mode(PhysicsServer2D::BODY_MODE_STATIC);
 	}
 
 	_update_kinematic_motion();
@@ -232,28 +232,30 @@ bool StaticBody2D::is_kinematic_motion_enabled() const {
 }
 
 void StaticBody2D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
+	switch (p_what) {
+		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
 #ifdef TOOLS_ENABLED
-		if (Engine::get_singleton()->is_editor_hint()) {
-			return;
-		}
+			if (Engine::get_singleton()->is_editor_hint()) {
+				return;
+			}
 #endif
 
-		ERR_FAIL_COND(!kinematic_motion);
+			ERR_FAIL_COND(!kinematic_motion);
 
-		real_t delta_time = get_physics_process_delta_time();
+			real_t delta_time = get_physics_process_delta_time();
 
-		Transform2D new_transform = get_global_transform();
+			Transform2D new_transform = get_global_transform();
 
-		new_transform.translate(constant_linear_velocity * delta_time);
-		new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time);
+			new_transform.translate(constant_linear_velocity * delta_time);
+			new_transform.set_rotation(new_transform.get_rotation() + constant_angular_velocity * delta_time);
 
-		PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
+			PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
 
-		// Propagate transform change to node.
-		set_block_transform_notify(true);
-		set_global_transform(new_transform);
-		set_block_transform_notify(false);
+			// Propagate transform change to node.
+			set_block_transform_notify(true);
+			set_global_transform(new_transform);
+			set_block_transform_notify(false);
+		} break;
 	}
 }
 
@@ -528,18 +530,18 @@ void RigidBody2D::set_mode(Mode p_mode) {
 	mode = p_mode;
 	switch (p_mode) {
 		case MODE_DYNAMIC: {
-			PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_DYNAMIC);
+			set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC);
 		} break;
 		case MODE_STATIC: {
-			PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_STATIC);
+			set_body_mode(PhysicsServer2D::BODY_MODE_STATIC);
 
 		} break;
 		case MODE_KINEMATIC: {
-			PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_KINEMATIC);
+			set_body_mode(PhysicsServer2D::BODY_MODE_KINEMATIC);
 
 		} break;
 		case MODE_DYNAMIC_LOCKED: {
-			PhysicsServer2D::get_singleton()->body_set_mode(get_rid(), PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED);
+			set_body_mode(PhysicsServer2D::BODY_MODE_DYNAMIC_LOCKED);
 
 		} break;
 	}
@@ -795,18 +797,19 @@ bool RigidBody2D::is_contact_monitor_enabled() const {
 
 void RigidBody2D::_notification(int p_what) {
 #ifdef TOOLS_ENABLED
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			set_notify_local_transform(true); //used for warnings and only in editor
-		}
-	}
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			if (Engine::get_singleton()->is_editor_hint()) {
+				set_notify_local_transform(true); //used for warnings and only in editor
+			}
+		} break;
 
-	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			update_configuration_warnings();
-		}
+		case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
+			if (Engine::get_singleton()->is_editor_hint()) {
+				update_configuration_warnings();
+			}
+		} break;
 	}
-
 #endif
 }
 
@@ -1261,26 +1264,28 @@ void CharacterBody2D::set_up_direction(const Vector2 &p_up_direction) {
 }
 
 void CharacterBody2D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		last_valid_transform = get_global_transform();
-
-		// Reset move_and_slide() data.
-		on_floor = false;
-		on_floor_body = RID();
-		on_ceiling = false;
-		on_wall = false;
-		motion_results.clear();
-		floor_velocity = Vector2();
-	}
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			last_valid_transform = get_global_transform();
+
+			// Reset move_and_slide() data.
+			on_floor = false;
+			on_floor_body = RID();
+			on_ceiling = false;
+			on_wall = false;
+			motion_results.clear();
+			floor_velocity = Vector2();
+		} break;
 
-	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
-		//used by sync to physics, send the new transform to the physics
-		Transform2D new_transform = get_global_transform();
-		PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
-		//but then revert changes
-		set_notify_local_transform(false);
-		set_global_transform(last_valid_transform);
-		set_notify_local_transform(true);
+		case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
+			// Used by sync to physics, send the new transform to the physics.
+			Transform2D new_transform = get_global_transform();
+			PhysicsServer2D::get_singleton()->body_set_state(get_rid(), PhysicsServer2D::BODY_STATE_TRANSFORM, new_transform);
+			// But then revert changes.
+			set_notify_local_transform(false);
+			set_global_transform(last_valid_transform);
+			set_notify_local_transform(true);
+		} break;
 	}
 }
 

+ 132 - 14
scene/3d/collision_object_3d.cpp

@@ -32,7 +32,6 @@
 
 #include "core/config/engine.h"
 #include "scene/scene_string_names.h"
-#include "servers/physics_server_3d.h"
 
 void CollisionObject3D::_notification(int p_what) {
 	switch (p_what) {
@@ -59,15 +58,22 @@ void CollisionObject3D::_notification(int p_what) {
 				PhysicsServer3D::get_singleton()->body_set_state(rid, PhysicsServer3D::BODY_STATE_TRANSFORM, get_global_transform());
 			}
 
-			RID space = get_world_3d()->get_space();
-			if (area) {
-				PhysicsServer3D::get_singleton()->area_set_space(rid, space);
-			} else {
-				PhysicsServer3D::get_singleton()->body_set_space(rid, space);
+			bool disabled = !is_enabled();
+
+			if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
+				_apply_disabled();
+			}
+
+			if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
+				RID space = get_world_3d()->get_space();
+				if (area) {
+					PhysicsServer3D::get_singleton()->area_set_space(rid, space);
+				} else {
+					PhysicsServer3D::get_singleton()->body_set_space(rid, space);
+				}
 			}
 
 			_update_pickable();
-			//get space
 		} break;
 
 		case NOTIFICATION_TRANSFORM_CHANGED: {
@@ -78,19 +84,34 @@ void CollisionObject3D::_notification(int p_what) {
 			}
 
 			_on_transform_changed();
-
 		} break;
+
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			_update_pickable();
-
 		} break;
+
 		case NOTIFICATION_EXIT_WORLD: {
-			if (area) {
-				PhysicsServer3D::get_singleton()->area_set_space(rid, RID());
-			} else {
-				PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
+			bool disabled = !is_enabled();
+
+			if (!disabled || (disable_mode != DISABLE_MODE_REMOVE)) {
+				if (area) {
+					PhysicsServer3D::get_singleton()->area_set_space(rid, RID());
+				} else {
+					PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
+				}
+			}
+
+			if (disabled && (disable_mode != DISABLE_MODE_REMOVE)) {
+				_apply_enabled();
 			}
+		} break;
 
+		case NOTIFICATION_DISABLED: {
+			_apply_disabled();
+		} break;
+
+		case NOTIFICATION_ENABLED: {
+			_apply_enabled();
 		} break;
 	}
 }
@@ -153,6 +174,79 @@ bool CollisionObject3D::get_collision_mask_bit(int p_bit) const {
 	return get_collision_mask() & (1 << p_bit);
 }
 
+void CollisionObject3D::set_disable_mode(DisableMode p_mode) {
+	if (disable_mode == p_mode) {
+		return;
+	}
+
+	bool disabled = is_inside_tree() && !is_enabled();
+
+	if (disabled) {
+		// Cancel previous disable mode.
+		_apply_enabled();
+	}
+
+	disable_mode = p_mode;
+
+	if (disabled) {
+		// Apply new disable mode.
+		_apply_disabled();
+	}
+}
+
+CollisionObject3D::DisableMode CollisionObject3D::get_disable_mode() const {
+	return disable_mode;
+}
+
+void CollisionObject3D::_apply_disabled() {
+	switch (disable_mode) {
+		case DISABLE_MODE_REMOVE: {
+			if (is_inside_tree()) {
+				if (area) {
+					PhysicsServer3D::get_singleton()->area_set_space(rid, RID());
+				} else {
+					PhysicsServer3D::get_singleton()->body_set_space(rid, RID());
+				}
+			}
+		} break;
+
+		case DISABLE_MODE_MAKE_STATIC: {
+			if (!area && (body_mode != PhysicsServer3D::BODY_MODE_STATIC)) {
+				PhysicsServer3D::get_singleton()->body_set_mode(rid, PhysicsServer3D::BODY_MODE_STATIC);
+			}
+		} break;
+
+		case DISABLE_MODE_KEEP_ACTIVE: {
+			// Nothing to do.
+		} break;
+	}
+}
+
+void CollisionObject3D::_apply_enabled() {
+	switch (disable_mode) {
+		case DISABLE_MODE_REMOVE: {
+			if (is_inside_tree()) {
+				RID space = get_world_3d()->get_space();
+				if (area) {
+					PhysicsServer3D::get_singleton()->area_set_space(rid, space);
+				} else {
+					PhysicsServer3D::get_singleton()->body_set_space(rid, space);
+				}
+			}
+		} break;
+
+		case DISABLE_MODE_MAKE_STATIC: {
+			if (!area && (body_mode != PhysicsServer3D::BODY_MODE_STATIC)) {
+				PhysicsServer3D::get_singleton()->body_set_mode(rid, body_mode);
+			}
+		} break;
+
+		case DISABLE_MODE_KEEP_ACTIVE: {
+			// Nothing to do.
+		} break;
+	}
+}
+
 void CollisionObject3D::_input_event(Node *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) {
 	if (get_script_instance()) {
 		get_script_instance()->call(SceneStringNames::get_singleton()->_input_event, p_camera, p_input_event, p_pos, p_normal, p_shape);
@@ -174,6 +268,22 @@ void CollisionObject3D::_mouse_exit() {
 	emit_signal(SceneStringNames::get_singleton()->mouse_exited);
 }
 
+void CollisionObject3D::set_body_mode(PhysicsServer3D::BodyMode p_mode) {
+	ERR_FAIL_COND(area);
+
+	if (body_mode == p_mode) {
+		return;
+	}
+
+	body_mode = p_mode;
+
+	if (is_inside_tree() && !is_enabled() && (disable_mode == DISABLE_MODE_MAKE_STATIC)) {
+		return;
+	}
+
+	PhysicsServer3D::get_singleton()->body_set_mode(rid, p_mode);
+}
+
 void CollisionObject3D::_update_pickable() {
 	if (!is_inside_tree()) {
 		return;
@@ -305,6 +415,8 @@ void CollisionObject3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("get_collision_layer_bit", "bit"), &CollisionObject3D::get_collision_layer_bit);
 	ClassDB::bind_method(D_METHOD("set_collision_mask_bit", "bit", "value"), &CollisionObject3D::set_collision_mask_bit);
 	ClassDB::bind_method(D_METHOD("get_collision_mask_bit", "bit"), &CollisionObject3D::get_collision_mask_bit);
+	ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &CollisionObject3D::set_disable_mode);
+	ClassDB::bind_method(D_METHOD("get_disable_mode"), &CollisionObject3D::get_disable_mode);
 	ClassDB::bind_method(D_METHOD("set_ray_pickable", "ray_pickable"), &CollisionObject3D::set_ray_pickable);
 	ClassDB::bind_method(D_METHOD("is_ray_pickable"), &CollisionObject3D::is_ray_pickable);
 	ClassDB::bind_method(D_METHOD("set_capture_input_on_drag", "enable"), &CollisionObject3D::set_capture_input_on_drag);
@@ -332,6 +444,8 @@ void CollisionObject3D::_bind_methods() {
 	ADD_SIGNAL(MethodInfo("mouse_entered"));
 	ADD_SIGNAL(MethodInfo("mouse_exited"));
 
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,MakeStatic,KeepActive"), "set_disable_mode", "get_disable_mode");
+
 	ADD_GROUP("Collision", "collision_");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_layer", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_layer", "get_collision_layer");
 	ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_mask", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collision_mask", "get_collision_mask");
@@ -339,6 +453,10 @@ void CollisionObject3D::_bind_methods() {
 	ADD_GROUP("Input", "input_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_ray_pickable"), "set_ray_pickable", "is_ray_pickable");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "input_capture_on_drag"), "set_capture_input_on_drag", "get_capture_input_on_drag");
+
+	BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE);
+	BIND_ENUM_CONSTANT(DISABLE_MODE_MAKE_STATIC);
+	BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE);
 }
 
 uint32_t CollisionObject3D::create_shape_owner(Object *p_owner) {
@@ -540,8 +658,8 @@ CollisionObject3D::CollisionObject3D(RID p_rid, bool p_area) {
 		PhysicsServer3D::get_singleton()->area_attach_object_instance_id(rid, get_instance_id());
 	} else {
 		PhysicsServer3D::get_singleton()->body_attach_object_instance_id(rid, get_instance_id());
+		PhysicsServer3D::get_singleton()->body_set_mode(rid, body_mode);
 	}
-	//set_transform_notify(true);
 }
 
 void CollisionObject3D::set_capture_input_on_drag(bool p_capture) {

+ 23 - 0
scene/3d/collision_object_3d.h

@@ -33,10 +33,19 @@
 
 #include "scene/3d/node_3d.h"
 #include "scene/resources/shape_3d.h"
+#include "servers/physics_server_3d.h"
 
 class CollisionObject3D : public Node3D {
 	GDCLASS(CollisionObject3D, Node3D);
 
+public:
+	enum DisableMode {
+		DISABLE_MODE_REMOVE,
+		DISABLE_MODE_MAKE_STATIC,
+		DISABLE_MODE_KEEP_ACTIVE,
+	};
+
+private:
 	uint32_t collision_layer = 1;
 	uint32_t collision_mask = 1;
 
@@ -44,6 +53,10 @@ class CollisionObject3D : public Node3D {
 
 	RID rid;
 
+	DisableMode disable_mode = DISABLE_MODE_REMOVE;
+
+	PhysicsServer3D::BodyMode body_mode = PhysicsServer3D::BODY_MODE_STATIC;
+
 	struct ShapeData {
 		Object *owner = nullptr;
 		Transform3D xform;
@@ -76,6 +89,9 @@ class CollisionObject3D : public Node3D {
 	void _update_debug_shapes();
 	void _clear_debug_shapes();
 
+	void _apply_disabled();
+	void _apply_enabled();
+
 protected:
 	CollisionObject3D(RID p_rid, bool p_area);
 
@@ -89,6 +105,8 @@ protected:
 	virtual void _mouse_enter();
 	virtual void _mouse_exit();
 
+	void set_body_mode(PhysicsServer3D::BodyMode p_mode);
+
 public:
 	void set_collision_layer(uint32_t p_layer);
 	uint32_t get_collision_layer() const;
@@ -102,6 +120,9 @@ public:
 	void set_collision_mask_bit(int p_bit, bool p_value);
 	bool get_collision_mask_bit(int p_bit) const;
 
+	void set_disable_mode(DisableMode p_mode);
+	DisableMode get_disable_mode() const;
+
 	uint32_t create_shape_owner(Object *p_owner);
 	void remove_shape_owner(uint32_t owner);
 	void get_shape_owners(List<uint32_t> *r_owners);
@@ -138,4 +159,6 @@ public:
 	~CollisionObject3D();
 };
 
+VARIANT_ENUM_CAST(CollisionObject3D::DisableMode);
+
 #endif // COLLISION_OBJECT__H

+ 60 - 55
scene/3d/physics_body_3d.cpp

@@ -65,7 +65,7 @@ void PhysicsBody3D::_bind_methods() {
 
 PhysicsBody3D::PhysicsBody3D(PhysicsServer3D::BodyMode p_mode) :
 		CollisionObject3D(PhysicsServer3D::get_singleton()->body_create(), false) {
-	PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), p_mode);
+	set_body_mode(p_mode);
 }
 
 PhysicsBody3D::~PhysicsBody3D() {
@@ -233,9 +233,9 @@ void StaticBody3D::set_kinematic_motion_enabled(bool p_enabled) {
 	kinematic_motion = p_enabled;
 
 	if (kinematic_motion) {
-		PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_KINEMATIC);
+		set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC);
 	} else {
-		PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_STATIC);
+		set_body_mode(PhysicsServer3D::BODY_MODE_STATIC);
 	}
 
 	_update_kinematic_motion();
@@ -282,35 +282,37 @@ Vector3 StaticBody3D::get_angular_velocity() const {
 }
 
 void StaticBody3D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) {
+	switch (p_what) {
+		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
 #ifdef TOOLS_ENABLED
-		if (Engine::get_singleton()->is_editor_hint()) {
-			return;
-		}
+			if (Engine::get_singleton()->is_editor_hint()) {
+				return;
+			}
 #endif
 
-		ERR_FAIL_COND(!kinematic_motion);
+			ERR_FAIL_COND(!kinematic_motion);
 
-		real_t delta_time = get_physics_process_delta_time();
+			real_t delta_time = get_physics_process_delta_time();
 
-		Transform3D new_transform = get_global_transform();
-		new_transform.origin += constant_linear_velocity * delta_time;
+			Transform3D new_transform = get_global_transform();
+			new_transform.origin += constant_linear_velocity * delta_time;
 
-		real_t ang_vel = constant_angular_velocity.length();
-		if (!Math::is_zero_approx(ang_vel)) {
-			Vector3 ang_vel_axis = constant_angular_velocity / ang_vel;
-			Basis rot(ang_vel_axis, ang_vel * delta_time);
-			new_transform.basis = rot * new_transform.basis;
-			new_transform.orthonormalize();
-		}
+			real_t ang_vel = constant_angular_velocity.length();
+			if (!Math::is_zero_approx(ang_vel)) {
+				Vector3 ang_vel_axis = constant_angular_velocity / ang_vel;
+				Basis rot(ang_vel_axis, ang_vel * delta_time);
+				new_transform.basis = rot * new_transform.basis;
+				new_transform.orthonormalize();
+			}
 
-		PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
+			PhysicsServer3D::get_singleton()->body_set_state(get_rid(), PhysicsServer3D::BODY_STATE_TRANSFORM, new_transform);
 
-		// Propagate transform change to node.
-		set_ignore_transform_notification(true);
-		set_global_transform(new_transform);
-		set_ignore_transform_notification(false);
-		_on_transform_changed();
+			// Propagate transform change to node.
+			set_ignore_transform_notification(true);
+			set_global_transform(new_transform);
+			set_ignore_transform_notification(false);
+			_on_transform_changed();
+		} break;
 	}
 }
 
@@ -598,18 +600,19 @@ void RigidBody3D::_direct_state_changed(Object *p_state) {
 
 void RigidBody3D::_notification(int p_what) {
 #ifdef TOOLS_ENABLED
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			set_notify_local_transform(true); //used for warnings and only in editor
-		}
-	}
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			if (Engine::get_singleton()->is_editor_hint()) {
+				set_notify_local_transform(true); //used for warnings and only in editor
+			}
+		} break;
 
-	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			update_configuration_warnings();
-		}
+		case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
+			if (Engine::get_singleton()->is_editor_hint()) {
+				update_configuration_warnings();
+			}
+		} break;
 	}
-
 #endif
 }
 
@@ -617,18 +620,16 @@ void RigidBody3D::set_mode(Mode p_mode) {
 	mode = p_mode;
 	switch (p_mode) {
 		case MODE_DYNAMIC: {
-			PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_DYNAMIC);
+			set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC);
 		} break;
 		case MODE_STATIC: {
-			PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_STATIC);
-
+			set_body_mode(PhysicsServer3D::BODY_MODE_STATIC);
 		} break;
 		case MODE_DYNAMIC_LOCKED: {
-			PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED);
-
+			set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC_LOCKED);
 		} break;
 		case MODE_KINEMATIC: {
-			PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_KINEMATIC);
+			set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC);
 		} break;
 	}
 	update_configuration_warnings();
@@ -1262,14 +1263,16 @@ void CharacterBody3D::set_up_direction(const Vector3 &p_up_direction) {
 }
 
 void CharacterBody3D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		// Reset move_and_slide() data.
-		on_floor = false;
-		on_floor_body = RID();
-		on_ceiling = false;
-		on_wall = false;
-		motion_results.clear();
-		floor_velocity = Vector3();
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			// Reset move_and_slide() data.
+			on_floor = false;
+			on_floor_body = RID();
+			on_ceiling = false;
+			on_wall = false;
+			motion_results.clear();
+			floor_velocity = Vector3();
+		} break;
 	}
 }
 
@@ -2121,7 +2124,8 @@ void PhysicalBone3D::_notification(int p_what) {
 				_reload_joint();
 			}
 			break;
-		case NOTIFICATION_EXIT_TREE:
+
+		case NOTIFICATION_EXIT_TREE: {
 			if (parent_skeleton) {
 				if (-1 != bone_id) {
 					parent_skeleton->unbind_physical_bone_from_bone(bone_id);
@@ -2131,12 +2135,13 @@ void PhysicalBone3D::_notification(int p_what) {
 			}
 			parent_skeleton = nullptr;
 			PhysicsServer3D::get_singleton()->joint_clear(joint);
-			break;
-		case NOTIFICATION_TRANSFORM_CHANGED:
+		} break;
+
+		case NOTIFICATION_TRANSFORM_CHANGED: {
 			if (Engine::get_singleton()->is_editor_hint()) {
 				update_offset();
 			}
-			break;
+		} break;
 	}
 }
 
@@ -2620,7 +2625,7 @@ void PhysicalBone3D::_start_physics_simulation() {
 		return;
 	}
 	reset_to_rest_position();
-	PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_DYNAMIC);
+	set_body_mode(PhysicsServer3D::BODY_MODE_DYNAMIC);
 	PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
 	PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
 	PhysicsServer3D::get_singleton()->body_set_force_integration_callback(get_rid(), callable_mp(this, &PhysicalBone3D::_direct_state_changed));
@@ -2633,11 +2638,11 @@ void PhysicalBone3D::_stop_physics_simulation() {
 		return;
 	}
 	if (parent_skeleton->get_animate_physical_bones()) {
-		PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_KINEMATIC);
+		set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC);
 		PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
 		PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
 	} else {
-		PhysicsServer3D::get_singleton()->body_set_mode(get_rid(), PhysicsServer3D::BODY_MODE_STATIC);
+		set_body_mode(PhysicsServer3D::BODY_MODE_STATIC);
 		PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), 0);
 		PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), 0);
 	}

+ 54 - 12
scene/3d/soft_body_3d.cpp

@@ -266,12 +266,13 @@ void SoftBody3D::_notification(int p_what) {
 			PhysicsServer3D::get_singleton()->soft_body_set_space(physics_rid, space);
 			prepare_physics_server();
 		} break;
+
 		case NOTIFICATION_READY: {
 			if (!parent_collision_ignore.is_empty()) {
 				add_collision_exception_with(get_node(parent_collision_ignore));
 			}
-
 		} break;
+
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 			if (Engine::get_singleton()->is_editor_hint()) {
 				_reset_points_offsets();
@@ -285,27 +286,36 @@ void SoftBody3D::_notification(int p_what) {
 			set_as_top_level(true);
 			set_transform(Transform3D());
 			set_notify_transform(true);
-
 		} break;
+
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			_update_pickable();
-
 		} break;
+
 		case NOTIFICATION_EXIT_WORLD: {
 			PhysicsServer3D::get_singleton()->soft_body_set_space(physics_rid, RID());
-
 		} break;
-	}
 
-#ifdef TOOLS_ENABLED
+		case NOTIFICATION_DISABLED: {
+			if (is_inside_tree() && (disable_mode == DISABLE_MODE_REMOVE)) {
+				prepare_physics_server();
+			}
+		} break;
 
-	if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
-		if (Engine::get_singleton()->is_editor_hint()) {
-			update_configuration_warnings();
-		}
-	}
+		case NOTIFICATION_ENABLED: {
+			if (is_inside_tree() && (disable_mode == DISABLE_MODE_REMOVE)) {
+				prepare_physics_server();
+			}
+		} break;
 
+#ifdef TOOLS_ENABLED
+		case NOTIFICATION_LOCAL_TRANSFORM_CHANGED: {
+			if (Engine::get_singleton()->is_editor_hint()) {
+				update_configuration_warnings();
+			}
+		} break;
 #endif
+	}
 }
 
 void SoftBody3D::_bind_methods() {
@@ -326,6 +336,9 @@ void SoftBody3D::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_parent_collision_ignore", "parent_collision_ignore"), &SoftBody3D::set_parent_collision_ignore);
 	ClassDB::bind_method(D_METHOD("get_parent_collision_ignore"), &SoftBody3D::get_parent_collision_ignore);
 
+	ClassDB::bind_method(D_METHOD("set_disable_mode", "mode"), &SoftBody3D::set_disable_mode);
+	ClassDB::bind_method(D_METHOD("get_disable_mode"), &SoftBody3D::get_disable_mode);
+
 	ClassDB::bind_method(D_METHOD("get_collision_exceptions"), &SoftBody3D::get_collision_exceptions);
 	ClassDB::bind_method(D_METHOD("add_collision_exception_with", "body"), &SoftBody3D::add_collision_exception_with);
 	ClassDB::bind_method(D_METHOD("remove_collision_exception_with", "body"), &SoftBody3D::remove_collision_exception_with);
@@ -364,6 +377,11 @@ void SoftBody3D::_bind_methods() {
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "drag_coefficient", PROPERTY_HINT_RANGE, "0,1,0.01"), "set_drag_coefficient", "get_drag_coefficient");
 
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "ray_pickable"), "set_ray_pickable", "is_ray_pickable");
+
+	ADD_PROPERTY(PropertyInfo(Variant::INT, "disable_mode", PROPERTY_HINT_ENUM, "Remove,KeepActive"), "set_disable_mode", "get_disable_mode");
+
+	BIND_ENUM_CONSTANT(DISABLE_MODE_REMOVE);
+	BIND_ENUM_CONSTANT(DISABLE_MODE_KEEP_ACTIVE);
 }
 
 TypedArray<String> SoftBody3D::get_configuration_warnings() const {
@@ -421,6 +439,7 @@ void SoftBody3D::_draw_soft_mesh() {
 }
 
 void SoftBody3D::prepare_physics_server() {
+#ifdef TOOLS_ENABLED
 	if (Engine::get_singleton()->is_editor_hint()) {
 		if (get_mesh().is_valid()) {
 			PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, get_mesh());
@@ -430,8 +449,9 @@ void SoftBody3D::prepare_physics_server() {
 
 		return;
 	}
+#endif
 
-	if (get_mesh().is_valid()) {
+	if (get_mesh().is_valid() && (is_enabled() || (disable_mode != DISABLE_MODE_REMOVE))) {
 		become_mesh_owner();
 		PhysicsServer3D::get_singleton()->soft_body_set_mesh(physics_rid, get_mesh());
 		RS::get_singleton()->connect("frame_pre_draw", callable_mp(this, &SoftBody3D::_draw_soft_mesh));
@@ -527,6 +547,28 @@ bool SoftBody3D::get_collision_layer_bit(int p_bit) const {
 	return get_collision_layer() & (1 << p_bit);
 }
 
+void SoftBody3D::set_disable_mode(DisableMode p_mode) {
+	if (disable_mode == p_mode) {
+		return;
+	}
+
+	bool inside_tree = is_inside_tree();
+
+	if (inside_tree && (disable_mode == DISABLE_MODE_REMOVE)) {
+		prepare_physics_server();
+	}
+
+	disable_mode = p_mode;
+
+	if (inside_tree && (disable_mode == DISABLE_MODE_REMOVE)) {
+		prepare_physics_server();
+	}
+}
+
+SoftBody3D::DisableMode SoftBody3D::get_disable_mode() const {
+	return disable_mode;
+}
+
 void SoftBody3D::set_parent_collision_ignore(const NodePath &p_parent_collision_ignore) {
 	parent_collision_ignore = p_parent_collision_ignore;
 }

+ 12 - 0
scene/3d/soft_body_3d.h

@@ -67,6 +67,11 @@ class SoftBody3D : public MeshInstance3D {
 	GDCLASS(SoftBody3D, MeshInstance3D);
 
 public:
+	enum DisableMode {
+		DISABLE_MODE_REMOVE,
+		DISABLE_MODE_KEEP_ACTIVE,
+	};
+
 	struct PinnedPoint {
 		int point_index = -1;
 		NodePath spatial_attachment_path;
@@ -83,6 +88,8 @@ private:
 
 	RID physics_rid;
 
+	DisableMode disable_mode = DISABLE_MODE_REMOVE;
+
 	bool mesh_owner = false;
 	uint32_t collision_mask = 1;
 	uint32_t collision_layer = 1;
@@ -137,6 +144,9 @@ public:
 	void set_collision_layer_bit(int p_bit, bool p_value);
 	bool get_collision_layer_bit(int p_bit) const;
 
+	void set_disable_mode(DisableMode p_mode);
+	DisableMode get_disable_mode() const;
+
 	void set_parent_collision_ignore(const NodePath &p_parent_collision_ignore);
 	const NodePath &get_parent_collision_ignore() const;
 
@@ -192,4 +202,6 @@ private:
 	int _has_pinned_point(int p_point_index) const;
 };
 
+VARIANT_ENUM_CAST(SoftBody3D::DisableMode);
+
 #endif // SOFT_PHYSICS_BODY_H

+ 23 - 20
scene/3d/vehicle_body_3d.cpp

@@ -79,26 +79,29 @@ public:
 };
 
 void VehicleWheel3D::_notification(int p_what) {
-	if (p_what == NOTIFICATION_ENTER_TREE) {
-		VehicleBody3D *cb = Object::cast_to<VehicleBody3D>(get_parent());
-		if (!cb) {
-			return;
-		}
-		body = cb;
-		local_xform = get_transform();
-		cb->wheels.push_back(this);
-
-		m_chassisConnectionPointCS = get_transform().origin;
-		m_wheelDirectionCS = -get_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
-		m_wheelAxleCS = get_transform().basis.get_axis(Vector3::AXIS_X).normalized();
-	}
-	if (p_what == NOTIFICATION_EXIT_TREE) {
-		VehicleBody3D *cb = Object::cast_to<VehicleBody3D>(get_parent());
-		if (!cb) {
-			return;
-		}
-		cb->wheels.erase(this);
-		body = nullptr;
+	switch (p_what) {
+		case NOTIFICATION_ENTER_TREE: {
+			VehicleBody3D *cb = Object::cast_to<VehicleBody3D>(get_parent());
+			if (!cb) {
+				return;
+			}
+			body = cb;
+			local_xform = get_transform();
+			cb->wheels.push_back(this);
+
+			m_chassisConnectionPointCS = get_transform().origin;
+			m_wheelDirectionCS = -get_transform().basis.get_axis(Vector3::AXIS_Y).normalized();
+			m_wheelAxleCS = get_transform().basis.get_axis(Vector3::AXIS_X).normalized();
+		} break;
+
+		case NOTIFICATION_EXIT_TREE: {
+			VehicleBody3D *cb = Object::cast_to<VehicleBody3D>(get_parent());
+			if (!cb) {
+				return;
+			}
+			cb->wheels.erase(this);
+			body = nullptr;
+		} break;
 	}
 }
 

+ 43 - 5
scene/main/node.cpp

@@ -403,6 +403,7 @@ void Node::set_process_mode(ProcessMode p_mode) {
 	}
 
 	bool prev_can_process = can_process();
+	bool prev_enabled = _is_enabled();
 
 	data.process_mode = p_mode;
 
@@ -417,6 +418,7 @@ void Node::set_process_mode(ProcessMode p_mode) {
 	}
 
 	bool next_can_process = can_process();
+	bool next_enabled = _is_enabled();
 
 	int pause_notification = 0;
 
@@ -426,7 +428,16 @@ void Node::set_process_mode(ProcessMode p_mode) {
 		pause_notification = NOTIFICATION_UNPAUSED;
 	}
 
-	_propagate_process_owner(data.process_owner, pause_notification);
+	int enabled_notification = 0;
+
+	if (prev_enabled && !next_enabled) {
+		enabled_notification = NOTIFICATION_DISABLED;
+	} else if (!prev_enabled && next_enabled) {
+		enabled_notification = NOTIFICATION_ENABLED;
+	}
+
+	_propagate_process_owner(data.process_owner, pause_notification, enabled_notification);
+
 #ifdef TOOLS_ENABLED
 	// This is required for the editor to update the visibility of disabled nodes
 	// It's very expensive during runtime to change, so editor-only
@@ -455,17 +466,21 @@ Node::ProcessMode Node::get_process_mode() const {
 	return data.process_mode;
 }
 
-void Node::_propagate_process_owner(Node *p_owner, int p_notification) {
+void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification) {
 	data.process_owner = p_owner;
 
-	if (p_notification != 0) {
-		notification(p_notification);
+	if (p_pause_notification != 0) {
+		notification(p_pause_notification);
+	}
+
+	if (p_enabled_notification != 0) {
+		notification(p_enabled_notification);
 	}
 
 	for (int i = 0; i < data.children.size(); i++) {
 		Node *c = data.children[i];
 		if (c->data.process_mode == PROCESS_MODE_INHERIT) {
-			c->_propagate_process_owner(p_owner, p_notification);
+			c->_propagate_process_owner(p_owner, p_pause_notification, p_enabled_notification);
 		}
 	}
 }
@@ -669,6 +684,27 @@ bool Node::_can_process(bool p_paused) const {
 	}
 }
 
+bool Node::_is_enabled() const {
+	ProcessMode process_mode;
+
+	if (data.process_mode == PROCESS_MODE_INHERIT) {
+		if (!data.process_owner) {
+			process_mode = PROCESS_MODE_PAUSABLE;
+		} else {
+			process_mode = data.process_owner->data.process_mode;
+		}
+	} else {
+		process_mode = data.process_mode;
+	}
+
+	return (process_mode != PROCESS_MODE_DISABLED);
+}
+
+bool Node::is_enabled() const {
+	ERR_FAIL_COND_V(!is_inside_tree(), false);
+	return _is_enabled();
+}
+
 float Node::get_physics_process_delta_time() const {
 	if (data.tree) {
 		return data.tree->get_physics_process_time();
@@ -2628,6 +2664,8 @@ void Node::_bind_methods() {
 	BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS);
 	BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
 	BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
+	BIND_CONSTANT(NOTIFICATION_DISABLED);
+	BIND_CONSTANT(NOTIFICATION_ENABLED);
 
 	BIND_CONSTANT(NOTIFICATION_EDITOR_PRE_SAVE);
 	BIND_CONSTANT(NOTIFICATION_EDITOR_POST_SAVE);

+ 5 - 1
scene/main/node.h

@@ -166,7 +166,7 @@ private:
 	void _propagate_after_exit_tree();
 	void _propagate_validate_owner();
 	void _print_stray_nodes();
-	void _propagate_process_owner(Node *p_owner, int p_notification);
+	void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
 	Array _get_node_and_resource(const NodePath &p_path);
 
 	void _duplicate_signals(const Node *p_original, Node *p_copy) const;
@@ -184,6 +184,7 @@ private:
 	void _propagate_pause_notification(bool p_enable);
 
 	_FORCE_INLINE_ bool _can_process(bool p_paused) const;
+	_FORCE_INLINE_ bool _is_enabled() const;
 
 protected:
 	void _block() { data.blocked++; }
@@ -227,6 +228,8 @@ public:
 		NOTIFICATION_INTERNAL_PROCESS = 25,
 		NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
 		NOTIFICATION_POST_ENTER_TREE = 27,
+		NOTIFICATION_DISABLED = 28,
+		NOTIFICATION_ENABLED = 29,
 		//keep these linked to node
 
 		NOTIFICATION_WM_MOUSE_ENTER = 1002,
@@ -385,6 +388,7 @@ public:
 	ProcessMode get_process_mode() const;
 	bool can_process() const;
 	bool can_process_notification(int p_what) const;
+	bool is_enabled() const;
 
 	void request_ready();