Sfoglia il codice sorgente

Merge pull request #39421 from RandomShaper/pause_aware_picking_3.2

Implement pause-aware picking (3.2)
Rémi Verschelde 4 anni fa
parent
commit
398a625a9f

+ 6 - 0
doc/classes/ProjectSettings.xml

@@ -1006,6 +1006,12 @@
 		<member name="physics/common/enable_object_picking" type="bool" setter="" getter="" default="true">
 		<member name="physics/common/enable_object_picking" type="bool" setter="" getter="" default="true">
 			Enables [member Viewport.physics_object_picking] on the root viewport.
 			Enables [member Viewport.physics_object_picking] on the root viewport.
 		</member>
 		</member>
+		<member name="physics/common/enable_pause_aware_picking" type="bool" setter="" getter="" default="false">
+			If enabled, 2D and 3D physics picking behaves this way in relation to pause:
+			- When pause is started, every collision object that is hovered or captured (3D only) is released from that condition, getting the relevant mouse-exit callback, unless its pause mode makes it immune to pause.
+			- During pause, picking only considers collision objects immune to pause, sending input events and enter/exit callbacks to them as expected.
+			If disabled, the legacy behavior is used, which consists in queuing the picking input events during pause (so nodes won't get them) and flushing that queue on resume, against the state of the 2D/3D world at that point.
+		</member>
 		<member name="physics/common/physics_fps" type="int" setter="" getter="" default="60">
 		<member name="physics/common/physics_fps" type="int" setter="" getter="" default="60">
 			The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run.
 			The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run.
 			[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.iterations_per_second] instead.
 			[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.iterations_per_second] instead.

+ 1 - 0
editor/project_manager.cpp

@@ -499,6 +499,7 @@ private:
 					initial_settings["application/config/name"] = project_name->get_text();
 					initial_settings["application/config/name"] = project_name->get_text();
 					initial_settings["application/config/icon"] = "res://icon.png";
 					initial_settings["application/config/icon"] = "res://icon.png";
 					initial_settings["rendering/environment/default_environment"] = "res://default_env.tres";
 					initial_settings["rendering/environment/default_environment"] = "res://default_env.tres";
+					initial_settings["physics/common/enable_pause_aware_picking"] = true;
 
 
 					if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("project.godot"), initial_settings, Vector<String>(), false) != OK) {
 					if (ProjectSettings::get_singleton()->save_custom(dir.plus_file("project.godot"), initial_settings, Vector<String>(), false) != OK) {
 						set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR);
 						set_message(TTR("Couldn't create project.godot in project path."), MESSAGE_ERROR);

+ 1 - 0
main/main.cpp

@@ -1194,6 +1194,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 	Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
 	Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
 	Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0));
 	Engine::get_singleton()->set_target_fps(GLOBAL_DEF("debug/settings/fps/force_fps", 0));
 	ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater"));
 	ProjectSettings::get_singleton()->set_custom_property_info("debug/settings/fps/force_fps", PropertyInfo(Variant::INT, "debug/settings/fps/force_fps", PROPERTY_HINT_RANGE, "0,120,1,or_greater"));
+	GLOBAL_DEF("physics/common/enable_pause_aware_picking", false);
 
 
 	GLOBAL_DEF("debug/settings/stdout/print_fps", false);
 	GLOBAL_DEF("debug/settings/stdout/print_fps", false);
 	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
 	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);

+ 3 - 0
scene/main/scene_tree.cpp

@@ -478,6 +478,9 @@ bool SceneTree::iteration(float p_time) {
 	emit_signal("physics_frame");
 	emit_signal("physics_frame");
 
 
 	_notify_group_pause("physics_process_internal", Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
 	_notify_group_pause("physics_process_internal", Node::NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
+	if (GLOBAL_GET("physics/common/enable_pause_aware_picking")) {
+		call_group_flags(GROUP_CALL_REALTIME, "_viewports", "_process_picking", true);
+	}
 	_notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS);
 	_notify_group_pause("physics_process", Node::NOTIFICATION_PHYSICS_PROCESS);
 	_flush_ugc();
 	_flush_ugc();
 	MessageQueue::get_singleton()->flush(); //small little hack
 	MessageQueue::get_singleton()->flush(); //small little hack

+ 259 - 229
scene/main/viewport.cpp

@@ -418,288 +418,302 @@ void Viewport::_notification(int p_what) {
 				VS::get_singleton()->multimesh_set_visible_instances(contact_3d_debug_multimesh, point_count);
 				VS::get_singleton()->multimesh_set_visible_instances(contact_3d_debug_multimesh, point_count);
 			}
 			}
 
 
-			if (physics_object_picking && (to_screen_rect == Rect2() || Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED)) {
+			if (!GLOBAL_GET("physics/common/enable_pause_aware_picking")) {
+				_process_picking(false);
+			}
+		} break;
+		case SceneTree::NOTIFICATION_WM_MOUSE_EXIT:
+		case SceneTree::NOTIFICATION_WM_FOCUS_OUT: {
 
 
-#ifndef _3D_DISABLED
-				Vector2 last_pos(1e20, 1e20);
-				CollisionObject *last_object = NULL;
-				ObjectID last_id = 0;
-#endif
-				PhysicsDirectSpaceState::RayResult result;
-				Physics2DDirectSpaceState *ss2d = Physics2DServer::get_singleton()->space_get_direct_state(find_world_2d()->get_space());
-
-				if (physics_has_last_mousepos) {
-					// if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved.
-					// while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame
-					bool has_mouse_event = false;
-					for (List<Ref<InputEvent> >::Element *E = physics_picking_events.front(); E; E = E->next()) {
-						Ref<InputEventMouse> m = E->get();
-						if (m.is_valid()) {
-							has_mouse_event = true;
-							break;
-						}
-					}
+			_drop_physics_mouseover();
 
 
-					if (!has_mouse_event) {
-						Ref<InputEventMouseMotion> mm;
-						mm.instance();
-						mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
-						mm->set_global_position(physics_last_mousepos);
-						mm->set_position(physics_last_mousepos);
-						mm->set_alt(physics_last_mouse_state.alt);
-						mm->set_shift(physics_last_mouse_state.shift);
-						mm->set_control(physics_last_mouse_state.control);
-						mm->set_metakey(physics_last_mouse_state.meta);
-						mm->set_button_mask(physics_last_mouse_state.mouse_mask);
-						physics_picking_events.push_back(mm);
-					}
-				}
+			if (gui.mouse_focus) {
+				//if mouse is being pressed, send a release event
+				_drop_mouse_focus();
+			}
+		} break;
+	}
+}
+
+void Viewport::_process_picking(bool p_ignore_paused) {
+
+	if (!is_inside_tree())
+		return;
+	if (!physics_object_picking)
+		return;
+	if (to_screen_rect != Rect2() && Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED)
+		return;
 
 
-				while (physics_picking_events.size()) {
+	if (p_ignore_paused) {
+		_drop_physics_mouseover(true);
+	}
 
 
-					Ref<InputEvent> ev = physics_picking_events.front()->get();
-					physics_picking_events.pop_front();
+#ifndef _3D_DISABLED
+	Vector2 last_pos(1e20, 1e20);
+	CollisionObject *last_object = NULL;
+	ObjectID last_id = 0;
+#endif
+	PhysicsDirectSpaceState::RayResult result;
+	Physics2DDirectSpaceState *ss2d = Physics2DServer::get_singleton()->space_get_direct_state(find_world_2d()->get_space());
+
+	if (physics_has_last_mousepos) {
+		// if no mouse event exists, create a motion one. This is necessary because objects or camera may have moved.
+		// while this extra event is sent, it is checked if both camera and last object and last ID did not move. If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame
+		bool has_mouse_event = false;
+		for (List<Ref<InputEvent> >::Element *E = physics_picking_events.front(); E; E = E->next()) {
+			Ref<InputEventMouse> m = E->get();
+			if (m.is_valid()) {
+				has_mouse_event = true;
+				break;
+			}
+		}
 
 
-					Vector2 pos;
-					bool is_mouse = false;
+		if (!has_mouse_event) {
+			Ref<InputEventMouseMotion> mm;
+			mm.instance();
+			mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
+			mm->set_global_position(physics_last_mousepos);
+			mm->set_position(physics_last_mousepos);
+			mm->set_alt(physics_last_mouse_state.alt);
+			mm->set_shift(physics_last_mouse_state.shift);
+			mm->set_control(physics_last_mouse_state.control);
+			mm->set_metakey(physics_last_mouse_state.meta);
+			mm->set_button_mask(physics_last_mouse_state.mouse_mask);
+			physics_picking_events.push_back(mm);
+		}
+	}
 
 
-					Ref<InputEventMouseMotion> mm = ev;
+	while (physics_picking_events.size()) {
 
 
-					if (mm.is_valid()) {
+		Ref<InputEvent> ev = physics_picking_events.front()->get();
+		physics_picking_events.pop_front();
 
 
-						pos = mm->get_position();
-						is_mouse = true;
+		Vector2 pos;
+		bool is_mouse = false;
 
 
-						physics_has_last_mousepos = true;
-						physics_last_mousepos = pos;
-						physics_last_mouse_state.alt = mm->get_alt();
-						physics_last_mouse_state.shift = mm->get_shift();
-						physics_last_mouse_state.control = mm->get_control();
-						physics_last_mouse_state.meta = mm->get_metakey();
-						physics_last_mouse_state.mouse_mask = mm->get_button_mask();
-					}
+		Ref<InputEventMouseMotion> mm = ev;
 
 
-					Ref<InputEventMouseButton> mb = ev;
+		if (mm.is_valid()) {
 
 
-					if (mb.is_valid()) {
+			pos = mm->get_position();
+			is_mouse = true;
 
 
-						pos = mb->get_position();
-						is_mouse = true;
+			physics_has_last_mousepos = true;
+			physics_last_mousepos = pos;
+			physics_last_mouse_state.alt = mm->get_alt();
+			physics_last_mouse_state.shift = mm->get_shift();
+			physics_last_mouse_state.control = mm->get_control();
+			physics_last_mouse_state.meta = mm->get_metakey();
+			physics_last_mouse_state.mouse_mask = mm->get_button_mask();
+		}
 
 
-						physics_has_last_mousepos = true;
-						physics_last_mousepos = pos;
-						physics_last_mouse_state.alt = mb->get_alt();
-						physics_last_mouse_state.shift = mb->get_shift();
-						physics_last_mouse_state.control = mb->get_control();
-						physics_last_mouse_state.meta = mb->get_metakey();
+		Ref<InputEventMouseButton> mb = ev;
 
 
-						if (mb->is_pressed()) {
-							physics_last_mouse_state.mouse_mask |= (1 << (mb->get_button_index() - 1));
-						} else {
-							physics_last_mouse_state.mouse_mask &= ~(1 << (mb->get_button_index() - 1));
+		if (mb.is_valid()) {
 
 
-							// If touch mouse raised, assume we don't know last mouse pos until new events come
-							if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) {
-								physics_has_last_mousepos = false;
-							}
-						}
-					}
+			pos = mb->get_position();
+			is_mouse = true;
 
 
-					Ref<InputEventKey> k = ev;
-					if (k.is_valid()) {
-						//only for mask
-						physics_last_mouse_state.alt = k->get_alt();
-						physics_last_mouse_state.shift = k->get_shift();
-						physics_last_mouse_state.control = k->get_control();
-						physics_last_mouse_state.meta = k->get_metakey();
-						continue;
-					}
+			physics_has_last_mousepos = true;
+			physics_last_mousepos = pos;
+			physics_last_mouse_state.alt = mb->get_alt();
+			physics_last_mouse_state.shift = mb->get_shift();
+			physics_last_mouse_state.control = mb->get_control();
+			physics_last_mouse_state.meta = mb->get_metakey();
 
 
-					Ref<InputEventScreenDrag> sd = ev;
+			if (mb->is_pressed()) {
+				physics_last_mouse_state.mouse_mask |= (1 << (mb->get_button_index() - 1));
+			} else {
+				physics_last_mouse_state.mouse_mask &= ~(1 << (mb->get_button_index() - 1));
 
 
-					if (sd.is_valid()) {
-						pos = sd->get_position();
-					}
+				// If touch mouse raised, assume we don't know last mouse pos until new events come
+				if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) {
+					physics_has_last_mousepos = false;
+				}
+			}
+		}
 
 
-					Ref<InputEventScreenTouch> st = ev;
+		Ref<InputEventKey> k = ev;
+		if (k.is_valid()) {
+			//only for mask
+			physics_last_mouse_state.alt = k->get_alt();
+			physics_last_mouse_state.shift = k->get_shift();
+			physics_last_mouse_state.control = k->get_control();
+			physics_last_mouse_state.meta = k->get_metakey();
+			continue;
+		}
 
 
-					if (st.is_valid()) {
-						pos = st->get_position();
-					}
+		Ref<InputEventScreenDrag> sd = ev;
 
 
-					if (ss2d) {
-						//send to 2D
+		if (sd.is_valid()) {
+			pos = sd->get_position();
+		}
 
 
-						uint64_t frame = get_tree()->get_frame();
+		Ref<InputEventScreenTouch> st = ev;
 
 
-						Physics2DDirectSpaceState::ShapeResult res[64];
-						for (Set<CanvasLayer *>::Element *E = canvas_layers.front(); E; E = E->next()) {
-							Transform2D canvas_transform;
-							ObjectID canvas_layer_id;
-							if (E->get()) {
-								// A descendant CanvasLayer
-								canvas_transform = E->get()->get_transform();
-								canvas_layer_id = E->get()->get_instance_id();
-							} else {
-								// This Viewport's builtin canvas
-								canvas_transform = get_canvas_transform();
-								canvas_layer_id = 0;
-							}
+		if (st.is_valid()) {
+			pos = st->get_position();
+		}
 
 
-							Vector2 point = canvas_transform.affine_inverse().xform(pos);
-
-							int rc = ss2d->intersect_point_on_canvas(point, canvas_layer_id, res, 64, Set<RID>(), 0xFFFFFFFF, true, true, true);
-							for (int i = 0; i < rc; i++) {
-
-								if (res[i].collider_id && res[i].collider) {
-									CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider);
-									if (co) {
-										bool send_event = true;
-										if (is_mouse) {
-											Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id);
-
-											if (!F) {
-												physics_2d_mouseover.insert(res[i].collider_id, frame);
-												co->_mouse_enter();
-											} else {
-												F->get() = frame;
-												// It was already hovered, so don't send the event if it's faked
-												if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) {
-													send_event = false;
-												}
-											}
-										}
-
-										if (send_event) {
-											co->_input_event(this, ev, res[i].shape);
-										}
-									}
-								}
-							}
-						}
+		if (ss2d) {
+			//send to 2D
 
 
-						if (is_mouse) {
-							List<Map<ObjectID, uint64_t>::Element *> to_erase;
+			uint64_t frame = get_tree()->get_frame();
 
 
-							for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
-								if (E->get() != frame) {
-									Object *o = ObjectDB::get_instance(E->key());
-									if (o) {
+			Physics2DDirectSpaceState::ShapeResult res[64];
+			for (Set<CanvasLayer *>::Element *E = canvas_layers.front(); E; E = E->next()) {
+				Transform2D canvas_transform;
+				ObjectID canvas_layer_id;
+				if (E->get()) {
+					// A descendant CanvasLayer
+					canvas_transform = E->get()->get_transform();
+					canvas_layer_id = E->get()->get_instance_id();
+				} else {
+					// This Viewport's builtin canvas
+					canvas_transform = get_canvas_transform();
+					canvas_layer_id = 0;
+				}
 
 
-										CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
-										if (co) {
-											co->_mouse_exit();
-										}
+				Vector2 point = canvas_transform.affine_inverse().xform(pos);
+
+				int rc = ss2d->intersect_point_on_canvas(point, canvas_layer_id, res, 64, Set<RID>(), 0xFFFFFFFF, true, true, true);
+				for (int i = 0; i < rc; i++) {
+
+					if (res[i].collider_id && res[i].collider) {
+						CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider);
+						if (co && (!p_ignore_paused || co->can_process())) {
+							bool send_event = true;
+							if (is_mouse) {
+								Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id);
+
+								if (!F) {
+									physics_2d_mouseover.insert(res[i].collider_id, frame);
+									co->_mouse_enter();
+								} else {
+									F->get() = frame;
+									// It was already hovered, so don't send the event if it's faked
+									if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) {
+										send_event = false;
 									}
 									}
-									to_erase.push_back(E);
 								}
 								}
 							}
 							}
 
 
-							while (to_erase.size()) {
-								physics_2d_mouseover.erase(to_erase.front()->get());
-								to_erase.pop_front();
+							if (send_event) {
+								co->_input_event(this, ev, res[i].shape);
 							}
 							}
 						}
 						}
 					}
 					}
+				}
+			}
 
 
-#ifndef _3D_DISABLED
-					bool captured = false;
+			if (is_mouse) {
+				List<Map<ObjectID, uint64_t>::Element *> to_erase;
 
 
-					if (physics_object_capture != 0) {
+				for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
+					if (E->get() != frame) {
+						Object *o = ObjectDB::get_instance(E->key());
+						if (o) {
 
 
-						CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_capture));
-						if (co && camera) {
-							_collision_object_input_event(co, camera, ev, Vector3(), Vector3(), 0);
-							captured = true;
-							if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) {
-								physics_object_capture = 0;
+							CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
+							if (co) {
+								co->_mouse_exit();
 							}
 							}
-
-						} else {
-							physics_object_capture = 0;
 						}
 						}
+						to_erase.push_back(E);
 					}
 					}
+				}
 
 
-					if (captured) {
-						//none
-					} else if (pos == last_pos) {
+				while (to_erase.size()) {
+					physics_2d_mouseover.erase(to_erase.front()->get());
+					to_erase.pop_front();
+				}
+			}
+		}
 
 
-						if (last_id) {
-							if (ObjectDB::get_instance(last_id) && last_object) {
-								//good, exists
-								_collision_object_input_event(last_object, camera, ev, result.position, result.normal, result.shape);
-								if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
-									physics_object_capture = last_id;
-								}
-							}
-						}
-					} else {
+#ifndef _3D_DISABLED
+		bool captured = false;
 
 
-						if (camera) {
+		if (physics_object_capture != 0) {
 
 
-							Vector3 from = camera->project_ray_origin(pos);
-							Vector3 dir = camera->project_ray_normal(pos);
+			CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_capture));
+			if (co && camera) {
+				_collision_object_input_event(co, camera, ev, Vector3(), Vector3(), 0);
+				captured = true;
+				if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) {
+					physics_object_capture = 0;
+				}
 
 
-							PhysicsDirectSpaceState *space = PhysicsServer::get_singleton()->space_get_direct_state(find_world()->get_space());
-							if (space) {
+			} else {
+				physics_object_capture = 0;
+			}
+		}
 
 
-								bool col = space->intersect_ray(from, from + dir * 10000, result, Set<RID>(), 0xFFFFFFFF, true, true, true);
-								ObjectID new_collider = 0;
-								if (col) {
+		if (captured) {
+			//none
+		} else if (pos == last_pos) {
 
 
-									CollisionObject *co = Object::cast_to<CollisionObject>(result.collider);
-									if (co) {
+			if (last_id) {
+				if (ObjectDB::get_instance(last_id) && last_object) {
+					//good, exists
+					_collision_object_input_event(last_object, camera, ev, result.position, result.normal, result.shape);
+					if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
+						physics_object_capture = last_id;
+					}
+				}
+			}
+		} else {
 
 
-										_collision_object_input_event(co, camera, ev, result.position, result.normal, result.shape);
-										last_object = co;
-										last_id = result.collider_id;
-										new_collider = last_id;
-										if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
-											physics_object_capture = last_id;
-										}
-									}
-								}
+			if (camera) {
 
 
-								if (is_mouse && new_collider != physics_object_over) {
+				Vector3 from = camera->project_ray_origin(pos);
+				Vector3 dir = camera->project_ray_normal(pos);
 
 
-									if (physics_object_over) {
+				PhysicsDirectSpaceState *space = PhysicsServer::get_singleton()->space_get_direct_state(find_world()->get_space());
+				if (space) {
 
 
-										CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over));
-										if (co) {
-											co->_mouse_exit();
-										}
-									}
+					bool col = space->intersect_ray(from, from + dir * 10000, result, Set<RID>(), 0xFFFFFFFF, true, true, true);
+					ObjectID new_collider = 0;
+					if (col) {
 
 
-									if (new_collider) {
+						CollisionObject *co = Object::cast_to<CollisionObject>(result.collider);
+						if (co && (!p_ignore_paused || co->can_process())) {
 
 
-										CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(new_collider));
-										if (co) {
-											co->_mouse_enter();
-										}
-									}
+							_collision_object_input_event(co, camera, ev, result.position, result.normal, result.shape);
+							last_object = co;
+							last_id = result.collider_id;
+							new_collider = last_id;
+							if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
+								physics_object_capture = last_id;
+							}
+						}
+					}
 
 
-									physics_object_over = new_collider;
-								}
+					if (is_mouse && new_collider != physics_object_over) {
+
+						if (physics_object_over) {
+
+							CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over));
+							if (co) {
+								co->_mouse_exit();
 							}
 							}
+						}
 
 
-							last_pos = pos;
+						if (new_collider) {
+
+							CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(new_collider));
+							if (co) {
+								co->_mouse_enter();
+							}
 						}
 						}
+
+						physics_object_over = new_collider;
 					}
 					}
-#endif
 				}
 				}
-			}
 
 
-		} break;
-		case SceneTree::NOTIFICATION_WM_MOUSE_EXIT:
-		case SceneTree::NOTIFICATION_WM_FOCUS_OUT: {
-
-			_drop_physics_mouseover();
-
-			if (gui.mouse_focus) {
-				//if mouse is being pressed, send a release event
-				_drop_mouse_focus();
+				last_pos = pos;
 			}
 			}
-		} break;
+		}
+#endif
 	}
 	}
 }
 }
 
 
@@ -2735,28 +2749,42 @@ void Viewport::_drop_mouse_focus() {
 	}
 	}
 }
 }
 
 
-void Viewport::_drop_physics_mouseover() {
+void Viewport::_drop_physics_mouseover(bool p_paused_only) {
 
 
 	physics_has_last_mousepos = false;
 	physics_has_last_mousepos = false;
 
 
-	while (physics_2d_mouseover.size()) {
-		Object *o = ObjectDB::get_instance(physics_2d_mouseover.front()->key());
+	List<Map<ObjectID, uint64_t>::Element *> to_erase;
+
+	for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
+		Object *o = ObjectDB::get_instance(E->key());
 		if (o) {
 		if (o) {
+
 			CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
 			CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
-			co->_mouse_exit();
+			if (co) {
+				if (p_paused_only && co->can_process()) {
+					continue;
+				}
+				co->_mouse_exit();
+				to_erase.push_back(E);
+			}
 		}
 		}
-		physics_2d_mouseover.erase(physics_2d_mouseover.front());
+	}
+
+	while (to_erase.size()) {
+		physics_2d_mouseover.erase(to_erase.front()->get());
+		to_erase.pop_front();
 	}
 	}
 
 
 #ifndef _3D_DISABLED
 #ifndef _3D_DISABLED
 	if (physics_object_over) {
 	if (physics_object_over) {
 		CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over));
 		CollisionObject *co = Object::cast_to<CollisionObject>(ObjectDB::get_instance(physics_object_over));
 		if (co) {
 		if (co) {
-			co->_mouse_exit();
+			if (!(p_paused_only && co->can_process())) {
+				co->_mouse_exit();
+				physics_object_over = physics_object_capture = 0;
+			}
 		}
 		}
 	}
 	}
-
-	physics_object_over = physics_object_capture = 0;
 #endif
 #endif
 }
 }
 
 
@@ -3297,6 +3325,8 @@ void Viewport::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("_own_world_changed"), &Viewport::_own_world_changed);
 	ClassDB::bind_method(D_METHOD("_own_world_changed"), &Viewport::_own_world_changed);
 
 
+	ClassDB::bind_method(D_METHOD("_process_picking", "ignore_paused"), &Viewport::_process_picking);
+
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arvr"), "set_use_arvr", "use_arvr");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "arvr"), "set_use_arvr", "use_arvr");
 
 
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
 	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");

+ 2 - 1
scene/main/viewport.h

@@ -406,7 +406,7 @@ private:
 	void _canvas_layer_remove(CanvasLayer *p_canvas_layer);
 	void _canvas_layer_remove(CanvasLayer *p_canvas_layer);
 
 
 	void _drop_mouse_focus();
 	void _drop_mouse_focus();
-	void _drop_physics_mouseover();
+	void _drop_physics_mouseover(bool p_paused_only = false);
 
 
 	void _update_canvas_items(Node *p_node);
 	void _update_canvas_items(Node *p_node);
 
 
@@ -414,6 +414,7 @@ private:
 
 
 protected:
 protected:
 	void _notification(int p_what);
 	void _notification(int p_what);
+	void _process_picking(bool p_ignore_paused);
 	static void _bind_methods();
 	static void _bind_methods();
 	virtual void _validate_property(PropertyInfo &property) const;
 	virtual void _validate_property(PropertyInfo &property) const;