Search code examples
javaandroidlibgdxquaternions

Quaternion rotations based on vector and point


I have a program I'm building that uses 3D.

It is basically made of an object (some kind of a cube) that users can rotate and move around, while I can watch their view of the object by inspecting the same object on the server with an arrow directed at their point of view.

The client sends it's location, direction facing, and up as 3 vectors to the server.

I am trying to get my arrow to be rendered based off their location and rotation.


I'm doing this with the following code (using LibGDX for 3D):

Vector3[] vs;
Vector3 tmp = new Vector3();
batch.begin(wm.cam);
for (SocketWrapper s : clientAtt.keySet()) {// for every client
    vs = clientAtt.get(s); // read the 3 vectors: { position, direction, up }

    tmp.set(vs[2].nor());
    vs[1].nor();

    // arr.transform is the transform of the arrow
    arr.transform.set(vs[0], new Quaternion().setFromCross(tmp.crs(Vector3.Z).nor(), vs[1]));

    batch.render(arr);
}

And this is the definition of arr:

arrow = new ModelBuilder().createArrow(0, 0, 0, 1, 0, 0, 0.1f, 0.1f, 5, GL20.GL_TRIANGLES, new Material(ColorAttribute.createDiffuse(Color.RED)), VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal);
arr = new ModelInstance(arrow);

If I only rotate around the Y axis, everything works, but if I use the X/Z axis it goes crazy.

I'm trying to figure out where my math is wrong and how to fix it.

Thanks.


Solution

  • Quaternion is used to define only a rotation, not the orientation. For example: it defines how to transform any given (unit) vector to another vector so that it is rotated the amount you specified. But it does not define which vector that is. Even more: there are an infinite amount of possible transformations that can achieve that.

    The setFromCross method lets you specify that rotation by providing two arbitrary vectors. The Quaternion will then be set so that it would transform the first vector to the second vector, by rotating it around an axis perpendicular to the vectors you provided.

    So, in your case:

    setFromCross(tmp.set(up).crs(Vector3.Z).nor(), direction)
    

    This sets the Quaternion so that it would rotate the cross product of your up vector and the Z+ vector to your direction vector, along the axis that is perpendicular to those two vectors. That might work for you in some cases, but I doubt that is what you actually want to do. So, to answer your question: that is probably where your math goes wrong.

    Although this goes beyond the scope of your question, let's look at how you could achieve what you want to achieve.

    1. First define the orientation of your model when it is not rotated. E.g. which side is up, which side is forward (direction) and which side is right (or left). Let's assume for this case that, in rest, your model's up side it Y+ (upRest = new Vector3(0, 1, 0);), it is facing X+ (directionRest = new Vector3(1, 0, 0)) and it's right is Z+ (rightRest = new Vector3(0, 0, 1);).

    2. Now define the rotated orientation you want to have. You already have that, except for the right for which we can use the cross product (perpendicular) vector: upTarget = new Vector3(vs[2]).nor(); directionTarget = new Vector3(vs[1]).nor(); rightTarget = new Vector3().set(upTarget).crs(directionTraget).nor(); Note that you might need to swap the up and direction target vectors in the cross product (.set(directionTarget).crs(upTarget).nor();)

    3. Because the orientation in rest is axis aligned, we can take a nice little shortcut by one of the properties of a matrix. A Matrix4 can be defined as: a vector of 4 vectors. The first of those four vectors specifies the rotated X axis, the second specifies the rotated Y axis, the third vector specifies the rotated Z vector and the fourth vector specifies the location. So, use this one-liner to set the model to the orientation and position we want:

      arr.transform.set(directionTarget, upTarget, rightTarget, vs[0]);