How to keep two objects in a stable relative position while rotating one of them (without groups, shared coordinates space)

I have a collection of boxes. Each box contains 2 attributes: position and rotation (both Euler vectors).

The box2 position and rotation were set relative to box1 using some custom editor. In the editor, the user aligned those 2 boxes, however, box1 position and rotation were fixed and set to [0, 0, 0] and [0, 0, 0].

I’m using those boxes on my scene but it turned out box1 can be rotated in real-time. I discovered that as long as this rotation is performed only on one axis, the boxes stay relatively fixed.

"Box 1 rotation = [0, 0, 0]"

However, when I start rotating box1 on the second axis the alignment breaks:

The code I use to establish box2 position and rotation based on box1 position and rotation looks like that:

const addVectors = (a: V, b: V): V => {
    return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
  };

  const box1 = {
    position: [0, 0, 0] as V,
    rotation
  };

  const p = addVectors(box1.position, [0.6, 0.8, 0]);

  const pV3 = new Vector3(...p);
  const xAxis = new Vector3(1, 0, 0);
  const yAxis = new Vector3(0, 1, 0);
  const zAxis = new Vector3(0, 0, 1);
  const [angleX, angleY, angleZ] = box1.rotation;
  const { x, y, z } = pV3
    .applyAxisAngle(xAxis, angleX)
    .applyAxisAngle(yAxis, angleY)
    .applyAxisAngle(zAxis, angleZ);
  const p2 = [x, y, z] as V;

  const box2 = {
    position: p2,
    rotation: addVectors(box1.rotation, [0, 0, degs[90]])
  };

I already learned that Euler vectors don’t like to be added (Transformations and Coordinate Systems | Discover three.js) but I cannot find any examples of anything similar to what I’m trying to accomplish here. I’ll be grateful for any hints or hacks that can help here :heart:

I prepared a codesandbox with simulation of the problem described above:

The crucial part starts at line 47.

After many desperate attempts I finally figured this out :smiley: It turned out quaternions are my best friends from today :heart_eyes:

This one is even more elegant and respects also the fact that the box1 can be translated by x, y, z (not only rotated)