X Tutup
Skip to content

Add vertex snapping to the 3D editor#117235

Open
ryevdokimov wants to merge 2 commits intogodotengine:masterfrom
Open-Industry-Project:vertex-snap
Open

Add vertex snapping to the 3D editor#117235
ryevdokimov wants to merge 2 commits intogodotengine:masterfrom
Open-Industry-Project:vertex-snap

Conversation

@ryevdokimov
Copy link
Contributor

@ryevdokimov ryevdokimov commented Mar 9, 2026

Hold the Vertex Snap shortcut (default: B) to highlight the nearest vertex on the selected node's geometry. Click and drag to snap that vertex to vertices on other meshes. For nodes without geometry (Camera3D, Marker3D, etc.), the node's origin snaps directly to the target vertex.

2026-03-08.21-00-36.mp4

@ryevdokimov ryevdokimov added this to the 4.x milestone Mar 9, 2026
@ryevdokimov ryevdokimov requested review from a team as code owners March 9, 2026 01:15
@ryevdokimov ryevdokimov requested a review from a team March 9, 2026 01:15
@jcostello
Copy link
Contributor

Works with imported meshes?

@ryevdokimov
Copy link
Contributor Author

Works with imported meshes?

Anything that uses TriangleMesh, imported or otherwise, which is most things with clickable and visible geometry.

@fire
Copy link
Member

fire commented Mar 9, 2026

I worked on the ux of https://github.com/jgillich/godot-snappy and the snapper has bitrotted, but I hope it can inspire enhancements to this pull request.

@passivestar
Copy link
Contributor

to highlight the nearest vertex on the selected node, then click and drag to snap it to vertices on other meshes

If I understand correctly this means you can only move one mesh at a time? It would be better to highlight nearest vertex on any visible mesh, then move all of the selected nodes using it as a reference point. This will allow moving multiple at once and moving non-meshes along with them.

This is how it works in blender:

blendersnapping.mp4

@ryevdokimov ryevdokimov marked this pull request as draft March 9, 2026 12:24
@ryevdokimov
Copy link
Contributor Author

This will allow moving multiple at once and moving non-meshes along with them.

Done.

2026-03-09.08-29-39.mp4

@ryevdokimov ryevdokimov marked this pull request as ready for review March 9, 2026 13:04
Vector3 source_display = vertex_snap_source;
if (vertex_snap_dragging) {
for (const KeyValue<ObjectID, Vector3> &E : vertex_snap_original_positions) {
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Node3D *node = ObjectDB::get_instance<Node3D>(E.key);


if (has_displacement) {
for (const KeyValue<ObjectID, Vector3> &E : vertex_snap_original_positions) {
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Node3D *node = ObjectDB::get_instance<Node3D>(E.key);

vertex_snap_has_target = false;

for (const KeyValue<ObjectID, Vector3> &E : vertex_snap_original_positions) {
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Node3D *node = ObjectDB::get_instance<Node3D>(E.key);

EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Vertex Snap"));
for (const KeyValue<ObjectID, Vector3> &E : original_positions) {
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Node3D *node = ObjectDB::get_instance<Node3D>(E.key);


void Node3DEditorViewport::_vertex_snap_commit() {
for (const KeyValue<ObjectID, Vector3> &E : vertex_snap_original_positions) {
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Node3D *node = Object::cast_to<Node3D>(ObjectDB::get_instance(E.key));
Node3D *node = ObjectDB::get_instance<Node3D>(E.key);

@ryevdokimov ryevdokimov force-pushed the vertex-snap branch 2 times, most recently from 9437f8b to d1d0c17 Compare March 9, 2026 19:46
Copy link
Member

@Calinou Calinou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested locally with a mix of CSG nodes, MeshInstance3Ds, Decals and Marker3Ds, it works as expected.

Some feedback:

  • It's possible to enable vertex snapping while dragging gizmos or using Blender-style manipulation shortcuts. However, it has no visible effect other than drawing the yellow circle gizmo around a vertex.

  • It should be possible to release B after starting a dragging operation, so you don't have to keep it held down the whole way. Right now, releasing B while dragging will stop moving the selected node (but won't cancel the operation).

  • The vertex snap gizmo could be drawn twice (once opaque with normal depth, once translucent with depth test disabled), so that you can be aware that you're snapping to a vertex that is occluded by another object:

Current Proposed (mockup)
Image Image

We use the same trick for the 3D selection box, so it appears translucent when occluded.

  • Non-geometry nodes like Decal, ReflectionProbe and Marker3D behave a bit oddly. You can enable vertex snapping and move the node according to the snapped vertex, but the node won't center around the vertex like one would probably expect. Instead, it will move relative to your mouse movement, taking the snapped vertex into account.

    • I can't think of a lot of use cases for this, so I think it would make more sense to move the node's origin to the snapped vertex instead. This makes sense for Marker3D nodes in particular, which may be used as "shot origins" on weapon models, characters, etc.
  • Out of curiosity, is edge/face snapping planned in the future?

@ryevdokimov
Copy link
Contributor Author

Feedback applied:

2026-03-09.21-37-52.mp4

Out of curiosity, is edge/face snapping planned in the future?

Yes, I do intend to provide, to the best of my ability, all the tools and features someone would expect from a 3D editor. 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add object snapping to the floor/vertices for the 3D editor viewport

7 participants

X Tutup