How to snap things together magnet-like using transform matrices?

I’ve got these sections of track that I want to snap together. Specifically, I want them to connect so that transform B and transform C are identical. How do I move transform A so that B and C become connected? I am using Godot’s Transform3D for all transforms.

Essentially, you’ll want to find A’s local transform with respect to B (as if B was the parent of A) and use that when setting A’s position.

extends Node3D

@onready var a_mesh: Node3D = $AMesh
@onready var b_mesh: Node3D = $BMesh
@onready var c_mesh: Node3D = $CMesh

var a_offset: Transform3D

func _ready() -> void:
	# Find A's local offset relative to B
	a_offset = a_mesh.transform * b_mesh.transform.affine_inverse()

func _process(delta: float) -> void:
	c_mesh.rotate_y(PI * delta)
	b_mesh.transform = c_mesh.transform
	# Apply A's local offset relative to B
	a_mesh.transform = b_mesh.transform * a_offset

You may want to use global_transform instead of transform, if these nodes do not share the same parent.

Before:

After:

Project: test.zip (5.6 KB)

This worked great. Thank you!

Update: I have run into trouble again. The above solution @exodrifter gave works in most cases, but there are some orientations where it does not work.

Due to a separate issue, I am using a slightly modified version of the solution code. Instead of snapping transforms, I first snap the basis and then I snap the position. Here is the basis snap code.

Snapping IN → OUT (works fine):

this_tube.global_basis = basis_magnet(
										this_tube.global_basis,
										this_snap_global_in.basis,
										that_snap_global_out.basis
)

Snapping OUT → IN (sometimes doesn’t work):

this_tube.global_basis = basis_magnet(
										this_tube.global_basis,
										this_snap_global_out.basis,
										that_snap_global_in.basis
)

Basis magnet:

func basis_magnet(my_basis: Basis, my_magnet: Basis, your_magnet: Basis) -> Basis:
	var my_offset: Basis = my_basis * my_magnet.inverse()
	return your_magnet * my_offset

Any more help is appreciated.

Second update, which was a long time coming:

Somehow, this function does the trick.

func desperate_magnet(my_xform: Transform3D, my_magnet: Transform3D, your_magnet: Transform3D) -> Transform3D:
	var my_xform_quat: Quaternion = my_xform.basis.get_rotation_quaternion().normalized()
	var my_mag_quat: Quaternion = my_magnet.basis.get_rotation_quaternion().normalized()
	var your_mag_quat: Quaternion = your_magnet.basis.get_rotation_quaternion().normalized()
	var quat_magic := Quaternion(my_mag_quat.inverse() * my_xform_quat).normalized()
	var new_bas := Basis(Quaternion(your_mag_quat * quat_magic).normalized()).orthonormalized()
	var new_xform := Transform3D(new_bas, my_xform.origin)
	return new_xform

I don’t know why the Basis methods didn’t work, but this does.