Merge pull request #102085 from GustJc/path3d_snap_collider

Add option for `Path3D` to snap to colliders
This commit is contained in:
Thaddeus Crews
2026-02-05 09:32:31 -06:00
2 changed files with 111 additions and 2 deletions

View File

@@ -114,6 +114,29 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con
Vector3 inters;
// Special case for primary handle, the handle id equals control point id.
const int idx = p_id;
if (!Path3DEditorPlugin::singleton->_edit.waiting_handle_physics || !Path3DEditorPlugin::singleton->_edit.in_physics_frame) {
Path3DEditorPlugin::singleton->_edit.waiting_handle_physics = true;
Path3DEditorPlugin::singleton->_edit.gizmo_handle = p_id;
Path3DEditorPlugin::singleton->_edit.gizmo_handle_secondary = p_secondary;
Path3DEditorPlugin::singleton->_edit.gizmo_camera = p_camera;
Path3DEditorPlugin::singleton->_edit.mouse_pos = p_point;
return;
// Only continue if inside physics frame and waiting for physics.
}
if (Path3DEditorPlugin::singleton->snap_to_collider) {
PhysicsDirectSpaceState3D *ss = p_camera->get_world_3d()->get_direct_space_state();
PhysicsDirectSpaceState3D::RayParameters ray_params;
ray_params.from = ray_from;
ray_params.to = ray_from + ray_dir * p_camera->get_far();
PhysicsDirectSpaceState3D::RayResult result;
if (ss->intersect_ray(ray_params, result)) {
Vector3 local = gi.xform(result.position);
c->set_point_position(idx, local);
return;
}
// Will continue and do the plane intersect_ray if doesn't hit anything.
}
if (p.intersects_ray(ray_from, ray_dir, &inters)) {
if (Node3DEditor::get_singleton()->is_snap_enabled()) {
float snap = Node3DEditor::get_singleton()->get_translate_snap();
@@ -669,10 +692,21 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
} else {
origin = gt.xform(c->get_point_position(c->get_point_count() - 1));
}
Plane p(p_camera->get_transform().basis.get_column(2), origin);
Vector3 ray_from = viewport->get_ray_pos(mbpos);
Vector3 ray_dir = viewport->get_ray(mbpos);
if (snap_to_collider) {
_edit.click_ray_pos = ray_from;
_edit.click_ray_dir = ray_dir * p_camera->get_far();
_edit.gizmo_camera = p_camera;
_edit.origin = origin;
_edit.waiting_point_physics = true;
return EditorPlugin::AFTER_GUI_INPUT_STOP;
}
Plane p(p_camera->get_transform().basis.get_column(2), origin);
Vector3 inters;
if (p.intersects_ray(ray_from, ray_dir, &inters)) {
ur->create_action(TTR("Add Point to Curve"));
@@ -748,6 +782,7 @@ void Path3DEditorPlugin::make_visible(bool p_visible) {
topmenu_bar->hide();
path = nullptr;
}
set_physics_process(p_visible);
}
void Path3DEditorPlugin::_mode_changed(int p_mode) {
@@ -791,6 +826,11 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) {
mirror_handle_length = !is_checked;
pm->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
} break;
case HANDLE_OPTION_SNAP_COLLIDER: {
bool is_checked = pm->is_item_checked(HANDLE_OPTION_SNAP_COLLIDER);
snap_to_collider = !is_checked;
pm->set_item_checked(HANDLE_OPTION_SNAP_COLLIDER, snap_to_collider);
} break;
}
}
@@ -884,6 +924,55 @@ void Path3DEditorPlugin::_notification(int p_what) {
path->update_gizmos();
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
if (_edit.waiting_point_physics) {
_edit.waiting_point_physics = false;
const Transform3D gt = path->get_global_transform();
const Transform3D it = gt.affine_inverse();
Ref<Curve3D> c = path->get_curve();
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state();
if (ss) {
PhysicsDirectSpaceState3D::RayParameters ray_params;
PhysicsDirectSpaceState3D::RayResult result;
ray_params.from = _edit.click_ray_pos;
ray_params.to = ray_params.from + _edit.click_ray_dir;
bool hit_something = false;
Vector3 inters;
if (ss->intersect_ray(ray_params, result)) {
inters = result.position;
hit_something = true;
} else {
Plane p(_edit.gizmo_camera->get_transform().basis.get_column(2), _edit.origin);
if (p.intersects_ray(ray_params.from, _edit.click_ray_dir, &inters)) {
hit_something = true;
}
}
if (hit_something) {
ur->create_action(TTR("Add Point to Curve"));
ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1);
ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count());
ur->commit_action();
}
}
}
if (_edit.waiting_handle_physics) {
_edit.in_physics_frame = true;
// Find gizmo reference.
Vector<Ref<Node3DGizmo>> gizmos = path->get_gizmos();
for (Ref<EditorNode3DGizmo> seg : gizmos) {
if (seg.is_valid()) {
_edit.gizmo = seg;
break;
}
}
_edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_camera, _edit.mouse_pos);
_edit.in_physics_frame = false;
_edit.waiting_handle_physics = false;
}
}
}
}
@@ -989,6 +1078,8 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
menu->set_item_checked(HANDLE_OPTION_ANGLE, mirror_handle_angle);
menu->add_check_item(TTR("Mirror Handle Lengths"));
menu->set_item_checked(HANDLE_OPTION_LENGTH, mirror_handle_length);
menu->add_check_item(TTR("Snap to Colliders"));
menu->set_item_checked(HANDLE_OPTION_SNAP_COLLIDER, snap_to_collider);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed));
curve_edit->set_pressed_no_signal(true);

View File

@@ -148,6 +148,7 @@ class Path3DEditorPlugin : public EditorPlugin {
bool handle_clicked = false;
bool mirror_handle_angle = true;
bool mirror_handle_length = true;
bool snap_to_collider = true;
void _create_curve();
void _confirm_clear_points();
@@ -157,7 +158,8 @@ class Path3DEditorPlugin : public EditorPlugin {
enum HandleOption {
HANDLE_OPTION_ANGLE,
HANDLE_OPTION_LENGTH
HANDLE_OPTION_LENGTH,
HANDLE_OPTION_SNAP_COLLIDER,
};
protected:
@@ -182,4 +184,20 @@ public:
void set_handle_clicked(bool clicked) { handle_clicked = clicked; }
Path3DEditorPlugin();
private:
struct EditData {
Vector3 click_ray_dir;
Vector3 click_ray_pos;
Vector3 origin;
Point2 mouse_pos;
bool show_rotation_line = false;
Ref<Path3DGizmo> gizmo;
int gizmo_handle = 0;
bool gizmo_handle_secondary = false;
Camera3D *gizmo_camera;
bool waiting_point_physics = false;
bool waiting_handle_physics = false;
bool in_physics_frame = false;
} _edit;
};