Calculating quaternion

I spend the last 3 days trying to figure out how to calculate the quaternion between 2 vectors (or planes really) and 2 other vectors/planes … and lost a good amount of my hair over it with no luck so far ;-/ … hopefully one of you awesome math wizards can tell me what i am doing wrong!

I am trying to place connectors (in this case hinges, consisting of hinge arm and hinge plate) between 2 cabinet panels. The calculation of direction (vector going into the panel) and materialdirection (vector of which way the material should point to given by the other panels surface) is correct.

The connector (hinges) are defined as pointing in V1(0,0,1) (going into the panels => direction) and U1(1,0,0) (pointing towards the other panel => materialdirection).
The quaternion i am looking for is the one that transforms U1/V1 into direction/materialdirection.

I tried with 2 different approaches, first one seems to work the best, second one not that great … but none works perfect for every case. Usually a handful of hingeplates/arms is always the wrong way around ;-/

public place1(
    position: Vector3,
    direction: Vector3,
    materialDirection: Vector3
  ) {
    this.position(position.x, position.y, position.z);

    var qrot = new Quaternion();
    qrot.setFromUnitVectors(new Vector3(0, 0, -1), direction);

    var mdir = new Vector3(-1, 0, 0).applyQuaternion(qrot);
    var angle = mdir.angleTo(materialDirection);

    var qrot2 = new Quaternion();
    qrot2.setFromAxisAngle(direction, angle);
    qrot.multiply(qrot2);

    var eul = new Euler();
    eul.setFromQuaternion(qrot);
    this.rotation(
      CabiGeometry.rad2Degree(eul.x),
      CabiGeometry.rad2Degree(eul.y),
      CabiGeometry.rad2Degree(eul.z)
    );
  }

and test 2:

  public place2(
    position: Vector3,
    direction: Vector3,
    materialDirection: Vector3
  ) {
    this.position(position.x, position.y, position.z);

    var u1 = new Vector3(1, 0, 0);
    var v1 = new Vector3(0, 0, -1);

    var u2 = materialDirection.clone();
    var v2 = direction.clone();

    var n1 = u1.cross(v1);
    var n2 = u2.cross(v2);
    n1.normalize();
    n2.normalize();

    var m: Vector3 = n1.clone().add(n2);
    m.normalize();

    var axis: Vector3 = m.clone().cross(n2);
    var angle = m.dot(n2);

    var qrot: Quaternion = new Quaternion(axis.x, axis.y, axis.z, angle);
    qrot.normalize();

    var eul = new Euler();
    eul.setFromQuaternion(qrot);
    this.rotation(
      CabiGeometry.rad2Degree(eul.x),
      CabiGeometry.rad2Degree(eul.y),
      CabiGeometry.rad2Degree(eul.z)
    );
  }

Any reason to not use lookAt? If the vectors don’t represent anything within the scene, just abstract positions, you can still create mock Object3Ds to do the calculations for you.

1 Like

Cross the two vectors to get the plane normal. Dot the two normals to get the angle. Cross the two normals to get the axis. Compute the quaternion (wow my iPhone is not aware of this word) fromAxisAngle.

Thank you for the suggestion - but a bit stumped how this can work. My understanding is that a single vector alone is not enough to calculate a proper rotation - you need at least a vector and an rotation angle?

Thank you … tried it, but the connectors are still rotated all over the place.

public place4(
    position: Vector3,
    direction: Vector3,
    materialDirection: Vector3
  ) {
    this.position(position.x, position.y, position.z);

    var v1 = new Vector3(1, 0, 0);
    var u1 = new Vector3(0, 0, 1);

    var v2 = materialDirection.clone();
    var u2 = direction.clone();

    var n1 = u1.cross(v1).normalize();
    var n2 = u2.cross(v2).normalize();

    var angle = n1.dot(n2);

    var angle = CabiGeometry.degree2Rad(CabiGeometry.rad2Degree(angle) + 10);

    console.log("** angle: " + CabiGeometry.rad2Degree(angle));
    var qrot: Quaternion = new Quaternion().setFromAxisAngle(
      n1.clone().cross(n2),
      angle
    );

    var eul = new Euler();
    eul.setFromQuaternion(qrot);
    this.rotation(
      CabiGeometry.rad2Degree(eul.x),
      CabiGeometry.rad2Degree(eul.y),
      CabiGeometry.rad2Degree(eul.z)
    );
  }

I believe you need an acos for the dot.

Ok, finally got it. Looks like mjurczyk first answer was the easiest !

final code that now works:

  public place(
    position: Vector3,
    direction: Vector3,
    materialDirection: Vector3
  ) {
    this.position(position.x, position.y, position.z);

    var mx4 = new Matrix4();
    mx4.lookAt(
      materialDirection,
      new Vector3(0, 0, 0),
      direction.clone().negate()
    );

    var eul = new Euler();
    eul.setFromRotationMatrix(mx4);

    this.rotation(
      CabiGeometry.rad2Degree(eul.x),
      CabiGeometry.rad2Degree(eul.y),
      CabiGeometry.rad2Degree(eul.z)
    );
  }

Dubois wasn’t wrong either … just that there are a lot of edge cases that need to be handled (such as when material direction is the same or exact opposite, in which case the cross-vectors don’t work). I almost got there with it, but it’s a lot of work … the MATRIX4.lookAt (which Object3d.lockAt is based on) is a lot easier.

thank you mjurczyk and dubois !

1 Like