How to rotate an object in Java 3D?

19,341

Solution 1

I finally figured out what I wanted to do by using Quaternions, which I learned about here: http://www.cs.uic.edu/~jbell/Courses/Eng591_F1999/outline_2.html Here's my solution.

Creating the cone:

 private void attachCone(float size) {
        Cone cone = new Cone(size, size* 2);

        // The group for rotation
        arrowheadRotationGroup = new TransformGroup();
        arrowheadRotationGroup.
             setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        arrowheadRotationGroup.addChild(cone);

        // The group for positioning the cone
        arrowheadPositionGroup = new TransformGroup();
        arrowheadPositionGroup. 
              setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        arrowheadPositionGroup.addChild(arrowheadRotationGroup);

        super.addChild(arrowheadPositionGroup);
    }

Now, when I want to rotate the cone to point in a certain direction specified as the vector from the point (0,0,0) to (direction.x, direction.y, direction.z), I use:

private final Vector3f yAxis = new Vector3f(0f, 1f, 0f);
private Vector3f direction; 

private void rotateCone() {
        // Get the normalized axis perpendicular to the direction 
        Vector3f axis = new Vector3f();
        axis.cross(yAxis, direction);
        axis.normalize();

        // When the intended direction is a point on the yAxis, rotate on x
        if (Float.isNaN(axis.x) && Float.isNaN(axis.y) && Float.isNaN(axis.z)) 
        {
            axis.x = 1f;
            axis.y = 0f;
            axis.z = 0f;
        }
        // Compute the quaternion transformations
        final float angleX = yAxis.angle(direction);
        final float a = axis.x * (float) Math.sin(angleX / 2f);
        final float b = axis.y * (float) Math.sin(angleX / 2f);
        final float c = axis.z * (float) Math.sin(angleX / 2f);
        final float d = (float) Math.cos(angleX / 2f);

        Transform3D t3d = new Transform3D();
        Quat4f quat = new Quat4f(a, b, c, d);
        t3d.set(quat);
        arrowheadRotationGroup.setTransform(t3d);

        Transform3D translateToTarget = new Transform3D();
        translateToTarget.setTranslation(this.direction);
        arrowheadPositionGroup.setTransform(translateToTarget);
    }

Solution 2

I'm just learning Java 3D myself at the moment, and from my current knowledge, the rotation methods set the transform to a rotation about that axis only. Therefore, if you wish to perform rotations about multiple axes, then you will need to use a second Transform3D.
ie:

Transform3D rotation = new Transform3D();
Transform3D temp = new Transform3D();

rotation.rotX(Math.PI/2);
temp.rotZ(Math.PI/2);
rotation.mul(temp); // multiply the 2 transformation matrices together.

As for the reason for Math.PI, this is because it uses radians instead of degrees, where Math.PI is equivalent to 180 degrees.

Finding the angle between your current orientation and your intended orientation isn't too hard - you could use Vector3fs, with the angle() method. A Vector would be set up with the initial orientation, and another in the intended. However, this doesn't tell you in which axes the angle lies. Doing so would require examination of the vectors to see which segments are set. [of course, there may be something that I am currently unaware of in the API]

Solution 3

This is not a java3D specific answer.

In general a matrix can be built such that there are 4 vectors that describe it.

1) A side (or lateral) vector
2) An up vector
3) A direction vector
4) A position

Each row of a 4x4 matrix.

Thus for a simple identity matrix we have the following matrix (I'll define a column major matrix, for a row major matrix all you need to do is swap the matrix indices around such that row 2 col 3 becomes row 3 col 2 throughout the matrix).

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

in this the first column is the side vector. The second column the up vector. The third the direction and the fourth the position.

Logically we can see that the vector (1, 0, 0, 0) points along the x axis (and thus is the side vector). The vector (0, 1, 0, 0) points along the y axis (and thus is the up vector). The third (0, 0, 1, 0) points along the Z-axis (and thus is the direction vector). The fourth (0, 0, 0, 1) indicates that the objects does not move at all.

Now lets say we wanted to face along the X-axis.

Obviously that would mean we have a vector of (1, 0, 0, 0 ) for our direction vector. Up would still be (0, 1, 0, 0) and position still 0, 0, 0 1. So what would our side vector be? Well, logically it would point along the z-axis. But which way? Well hold your fingers such that one finger points forward, one to the side and one up. Now rotate so that the forward finger is facing the same direction as the side pointing finger. Which way is the side pointing finger pointing now? The opposite direction to the original direction pointing finger. Thus the matrix is

 0 0 1 0
 0 1 0 0
-1 0 0 0
 0 0 0 1

At this point things seemingly get a little more complicated. It is simple enough to take an arbitrary position and an arbitrary point to look at (I'll call them vPos and vFocus). It is easy enough to form a vector from vPos to vFocus by subtracting vPos from vFocus (vFocus.x - vPos.x, vFocus.y - vPos.y, vFocus.z - vPos.z, vFocus.w - vPos.w ). Bear in mind all positions should be defined with a '1' in the w position where all directions should have a '0'. This is automatically taken care of when you do the subtraction above as the 1 in both ws will cancel out and leave 0. Anyway, we now have a vector pointing from the position towards vFocus we'll call it vDir. Unfortunately it has the length of the difference between vPos and vFocus. However if we divide the vDir vector by its length (vDir.x / length, vDir.y / length, vDir.z / length, vDir.w / length) then we normalise it and we have a direction with a total length of 1.

At this ponit we now have our 3rd and 4th columns of our matrix. Now, lets assuem up is still (0, 1, 0, 0) or vUp. We can assume that the crossproduct of the direction and vUp will produce a vector that is perpendicular (and also of unit length) to the plane formed by vDir and vUp. This gives us our side vector or vLat. Now .. we did kind of assume the up vector so its not strictly correct. We can now calculate it exactly by taking the cross product of vLat and vDir and we have all 4 vectors.

The final matrix is thus defined as follows

vLat.x vUp.x vDir.x vPos.x
vLat.y vUp.y vDir.y vPos.y
vLat.z vUp.z vDir.z vPos.z
vLat.w vUp.w vDir.w vPos.w

This isn't strictly the full answer as you will get problems as you look towards a point near to your (0, 1, 0, 0) vector but that should work for most cases.

Share:
19,341
Cuga
Author by

Cuga

Programming is easy. The challenge is in understanding the problem.

Updated on June 04, 2022

Comments

  • Cuga
    Cuga almost 2 years

    I have a Cone I drew in Java 3D with the following code:

    Cone cone = new Cone(2f, 3f);
    
    Transform3D t3d = new Transform3D();
    TransformGroup coneTransform = new TransformGroup(t3d);
    coneTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
    
    t3d.setTranslation(new Vector3f(0f,0f,0f);
    coneTransform.setTransform(t3d);
    coneTransform.addChild(cone);
    
    this.addChild(coneTransform);
    

    Suppose I have the cone sitting at point (1,1,1) and I want the tip of the cone to point down an imaginary line running through (0,0,0) and (1,1,1)... how can I do this?

    Here's an example of what I've been trying:

    Transform3D t3d = new Transform3D();  
    
    Vector3f direction = new Vector3f(1,2,1);    
    
    final double angleX = direction.angle(new Vector3f(1,0,0));
    final double angleY = direction.angle(new Vector3f(0,1,0));
    final double angleZ = direction.angle(new Vector3f(0,0,1));
    
    t3d.rotX(angleX);
    t3d.rotY(angleY);
    t3d.rotZ(angleZ);
    
    t3d.setTranslation(direction);
    
    coneTransform.setTransform(t3d);
    

    Thanks in advance for all help!