Godot Version
v4.4.1.stable.mono.official [49a5bc7b6]
Question
We have a game where we can pick up objects and stick them together to build bigger objects.
The locations on the objects that they can connect are defined by an Area3D componentized into a custom component called “SnapComponent”.
When they stick together we want the snap components to be aligned face to face with no rotation, but be able to have a “twist” rotation which is dependent on the angle that the connecting object was when connecting.
So if I were to stick two blocks together straight on it would look like this:
But if the red block was rotated slightly when attaching it would look like this:
The lines pointing out of the blocks are where the snap components are and the direction they face.
The following code is what we currently have that works to align the snaps face to face with no rotation:
Basis heldOriginalBasis = heldObject.GlobalTransform.Basis;
// Fetch the local transforms of connection areas relative to their respective bodies
Transform3D a1Local = tableObject.GlobalTransform.AffineInverse() * tableSnap.GlobalTransform;
Transform3D a2Local = heldObject.GlobalTransform.AffineInverse() * heldSnap.GlobalTransform;
Vector3 outward = a2Local.Basis.Z;
Transform3D a2Flipped = a2Local;
a2Flipped.Basis = a2Flipped.Basis.Rotated(outward, Mathf.Pi); // 180° around Y-axis
Transform3D alignment = a1Local * a2Flipped.AffineInverse();
heldObject.GlobalTransform = tableObject.GlobalTransform * alignment;
Transform3D newTransform = tableObject.GlobalTransform.AffineInverse() * heldObject.GlobalTransform;
Where heldObject and heldSnap are the object and snap component being placed on the object on the table which is tableObject and tableSnap.
“newTransform” then gets applied to the pieces of heldObject that get put on tableObject.
This code works but it does not apply any kind of “twist” so if I were to flip the red block upside-down before attaching, it would flip back upright when attaching again.
We have tried using the following code to achieve the twist rotation but it only works in certain cases:
/// <summary>
/// Given a rotational basis, snap to the nearest increment of the given degrees on all axes.
/// </summary>
private Basis SnapRotationToDegrees(Basis basis, float degrees) {
// Convert basis to Euler angles
Vector3 euler = basis.GetEuler();
// Snap each axis to nearest degrees
float roundedAngle = Mathf.DegToRad(degrees);
euler.X = Mathf.Round(euler.X / roundedAngle) * roundedAngle;
euler.Y = Mathf.Round(euler.Y / roundedAngle) * roundedAngle;
euler.Z = Mathf.Round(euler.Z / roundedAngle) * roundedAngle;
// Fix floating point issues
float limit = 1e-6f;
if (Mathf.Abs(euler.X) < limit) euler.X = 0.0f;
if (Mathf.Abs(euler.Y) < limit) euler.Y = 0.0f;
if (Mathf.Abs(euler.Z) < limit) euler.Z = 0.0f;
// Convert back to basis and return conversion
return Basis.FromEuler(euler);
}
and then this after the first block of code I shared
Basis snappedBasis = SnapRotationToDegrees(heldOriginalBasis, 30);
newTransform = new Transform3D(snappedBasis, newTransform.Origin);
This code works when the snap component is at certain angles. For example if I were to rotate the red block as I had for the second image but then placed it on the top of the blue block it rotates it like this:
I believe this is due to the axis in which it rotates does not change depending on the side of the block it is connected.
My initial thought was that the line Vector3 outward = a2Local.Basis.Z;
needed to be dependent on the side of the block but when changing this to anything other than Z it has even worse effects. Changing it to X makes the red block connect inside the blue block. And changing it to Y makes it’s rotation off in more than one axis weirdly.
If anyone has worked with this kind of rotational calculations before and has an idea on how to do this any help is welcome. If more context is needed please ask.