Repository: godotengine/godot
File: editor/scene/3d/skeleton_3d_editor_plugin.cpp (C++)

Code before:
	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object);
	ERR_FAIL_NULL(skeleton);

	skel_editor = memnew(Skeleton3DEditor(this, skeleton));
	add_custom_control(skel_editor);
}

Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() {
	skeleton_plugin = memnew(EditorInspectorPluginSkeleton);

	EditorInspector::add_inspector_plugin(skeleton_plugin);

	Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin));
	Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
}

EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
	Node3DEditor *ne = Node3DEditor::get_singleton();
	if (se && se->is_edit_mode()) {
		const Ref<InputEventMouseButton> mb = p_event;
		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
			Skeleton3D *skeleton = se->get_skeleton();
			if (skeleton) {
				real_t grab_threshold = 8 * EDSCALE;
				Vector3 ray_from = p_camera->get_global_transform().origin;
				Transform3D gt = skeleton->get_global_transform();
				int closest_idx = -1;
				real_t closest_dist = 1e10;
				const int bone_count = skeleton->get_bone_count();
				for (int i = 0; i < bone_count; i++) {
					Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin);
					Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
					real_t dist_3d = ray_from.distance_to(joint_pos_3d);
					real_t dist_2d = mb->get_position().distance_to(joint_pos_2d);
					if (dist_2d < grab_threshold && dist_3d < closest_dist) {
						closest_dist = dist_3d;
						closest_idx = i;
					}
				}

				if (closest_idx >= 0) {
					se->select_bone(closest_idx);
					se->update_bone_original();

					Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos();
					for (Ref<EditorNode3DGizmo> seg : gizmos) {
						if (seg.is_valid()) {
							Transform3D bone_xform = seg->get_subgizmo_transform(closest_idx);
							ne->call("_set_subgizmo_selection", skeleton, seg, closest_idx, bone_xform);
							break;

Review comment (bug):
```suggestion
		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
			Skeleton3D *skeleton = se->get_skeleton();
			if (skeleton) {
				int closest_idx = Skeleton3DGizmoPlugin::skeleton_intersect_ray(skeleton, p_camera, mb->get_position());
				if (closest_idx >= 0) {
```

To avoid duplication.

**skeleton_3d_editor_plugin.h**
```cpp
	static int skeleton_intersect_ray(const Skeleton3D *p_skeleton, Camera3D *p_camera, const Vector2 &p_point);
	virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override;
	virtual Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override;
	virtual void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override;
	virtual void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override;
```

**skeleton_3d_editor_plugin.cpp**
```cpp
int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const {
	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_node_3d());
	ERR_FAIL_NULL_V(skeleton, -1);
	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
	if (!se || !se->is_edit_mode()) {
		return -1;
	}
	int closest_idx = skeleton_intersect_ray(skeleton, p_camera, p_point);
	if (closest_idx >= 0) {
		se->select_bone(closest_idx);
		se->update_bone_original();
		return closest_idx;
	}
	se->select_bone(-1);
	return -1;
}

int Skeleton3DGizmoPlugin::skeleton_intersect_ray(const Skeleton3D *p_skeleton, Camera3D *p_camera, const Vector2 &p_point) {
	real_t grab_threshold = 8 * EDSCALE;
	Vector3 ray_from = p_camera->get_global_transform().origin;
	Transform3D gt = p_skeleton->get_global_transform();
	int closest_idx = -1;
	real_t closest_dist = 1e10;
	const int bone_count = p_skeleton->get_bone_count();
	for (int i = 0; i < bone_count; i++) {
		Vector3 joint_pos_3d = gt.xform(p_skeleton->get_bone_global_pose(i).origin);
		Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d);
		real_t dist_3d = ray_from.distance_to(joint_pos_3d);
		real_t dist_2d = p_point.distance_to(joint_pos_2d);
		if (dist_2d < grab_threshold && dist_3d < closest_dist) {
			closest_dist = dist_3d;
			closest_idx = i;
		}
	}
	return closest_idx;
};
```

Code after:
	Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object);
	ERR_FAIL_NULL(skeleton);

	skel_editor = memnew(Skeleton3DEditor(this, skeleton));
	add_custom_control(skel_editor);
}

Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() {
	skeleton_plugin = memnew(EditorInspectorPluginSkeleton);

	EditorInspector::add_inspector_plugin(skeleton_plugin);

	Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin));
	Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
}

EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
	Skeleton3DEditor *se = Skeleton3DEditor::get_singleton();
	Node3DEditor *ne = Node3DEditor::get_singleton();
	if (se && se->is_edit_mode()) {
		const Ref<InputEventMouseButton> mb = p_event;
		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) {
			Skeleton3D *skeleton = se->get_skeleton();
			if (skeleton) {
				int closest_idx = Skeleton3DGizmoPlugin::skeleton_intersect_ray(skeleton, p_camera, mb->get_position());
				if (closest_idx >= 0) {
					se->select_bone(closest_idx);
					se->update_bone_original();

					Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos();
					for (Ref<EditorNode3DGizmo> seg : gizmos) {
						if (seg.is_valid()) {
							Transform3D bone_xform = seg->get_subgizmo_transform(closest_idx);
							ne->call("_set_subgizmo_selection", skeleton, seg, closest_idx, bone_xform);
							break;
						}
					}
					return EditorPlugin::AFTER_GUI_INPUT_STOP;
				}
			}
		}
		if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) {
			if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) {
				if (!ne->is_gizmo_visible()) {
					return EditorPlugin::AFTER_GUI_INPUT_STOP;
				}
			}
		}
		return EditorPlugin::AFTER_GUI_INPUT_CUSTOM;
	}
	return EditorPlugin::AFTER_GUI_INPUT_PASS;