简体   繁体   中英

Get 3D Rotation Axis and Angle From 2D Joystick

I have a 2D virtual joystick on the screen in the bottom left hand corner. I have a sphere drawn in 3D at the origin (0, 0, 0)... My camera can orbit the sphere. I'm trying to move the camera using the joystick, but have no idea how I can do that. I need to create an axis-angle rotation from my joystick and update the camera angle which uses a quaternion to represent it's orientation. Here is what I currently have:

My camera rotation is stored as a quaternion:

// The current rotation
public Quaternion rotation = new Quaternion();

// The temp quaternion for the new rotation
private Quaternion newRotation = new Quaternion();

The rotation and camera are updated via these methods:

// Update the camera using the current rotation
public void update(boolean updateFrustum)
{
    float aspect = camera.viewportWidth / camera.viewportHeight;
    camera.projection.setToProjection(Math.abs(camera.near), Math.abs(camera.far), camera.fieldOfView, aspect);
    camera.view.setToLookAt(camera.position, tmp.set(camera.position).add(camera.direction), camera.up);

    // Rotate the current view matrix using our rotation quaternion
    camera.view.rotate(rotation);

    camera.combined.set(camera.projection);
    Matrix4.mul(camera.combined.val, camera.view.val);

    if (updateFrustum)
    {
        camera.invProjectionView.set(camera.combined);
        Matrix4.inv(camera.invProjectionView.val);
        camera.frustum.update(camera.invProjectionView);
    }
}

public void updateRotation(float axisX, float axisY, float axisZ, float speed)
{
    // Update rotation quaternion
    newRotation.setFromAxis(Vector3.tmp.set(axisX, axisY, axisZ), ROTATION_SPEED * speed * MathHelper.PIOVER180);
    rotation.mul(newRotation);

    // Update the camera
    update(true);
}

I am currently calling updateRotation() like this:

// Move the camera
if (joystick.isTouched)
{
    // Here is where I'm having trouble...
    // Get the current axis of the joystick to rotate around
    tmpAxis = joystick.getAxis();
    axisX = tmpAxis.X;
    axisY = tmpAxis.Y;

    // Update the camera with the new axis-angle of rotation

    // joystick.getSpeed() is just calculating the distance from 
    // the start point to current position of the joystick so that the 
    // rotation will be slower when closer to where it started and faster 
    // as it moves toward its max bounds

    controller.updateRotation(axisX, axisY, axisZ, joystick.getSpeed());
}

My current getAxis() method from the Joystick class:

public Vector2d getAxis()
{
    Vector2d axis = new Vector2d(0.0f, 0.0f);
    float xOffset = 0;
    float yOffset = 0;
    float angle = getAngle();

    // Determine x offset
    xOffset = 1f;

    // Determine y offset
    yOffset = 1f;

    // Determine positive or negative x offset
    if (angle > 270 || angle < 90)
    {
        // Upper left quadrant
        axis.X = xOffset;
    }
    else
    {
        axis.X = -xOffset;
    }

    // Determine positive or negative y offset
    if (angle > 180 && angle < 360)
    {
        // Upper left quadrant
        axis.Y = yOffset;
    }
    else
    {
        axis.Y = -yOffset;
    }

    return axis;
}

I would just use 3 vectors to define the camera: position, forward, up (right = cross(forward, up)). Then for your case you are always looking towards (0,0,0) so just update these 3 vectors on the input like this:

Go up/down:

right = cross(forward, up);
posititon = normalized(position + up*(input*zoomFactor)) * length(position); //zoomFactor might be useful if you are looking from close to reduce the move but is optional
forward = normalized((0,0,0)-position);
up = cross(right, forward);//should already be normalized Note: might be cross(forward, right)

Go left/right:

right = cross(forward, up);
position = normalized(position + right*(input*zoomFactor)) * length(position);
forward = normalized((0,0,0)-position);
right = cross(forward, up); //you do need to update it
up = cross(right, forward); //Note: might be cross(forward, right)

Tilt left/right:

up = normalize(up + right*input); //no zoom factor needed here

Zoom in/out:

position = position + forward*input;//in your case you might just want to set it to some percentage: position = position + forward*(length(position) * (input));

This approach should only work nice for small input values witch is usually the case. The angle change relative to input would be alpha = atan(input) so if input was infinity the angle would change for 90 degrees. But you can easily save/load the state and you can just manually set camera position and companion vectors. So you have everything you need to call "lookAt".

You probably want to update the rotation based on its current rotation state instead of just rotating over a fixed global paris of axis (up&right) I mean, at start position for example the camera looks towards the (0,0,1) vector:

  • object is at (0,0,0)
  • cam is at (0,0,-2)
  • cam dir points to (0, 0, 1)

If you push the joystick up 90º, the camera should:

  • move to the (0, 2, 0) position
  • cam dir points now to (0, -1, 0)

If you now push the joystick ro the right 90º, the camera should look like this:

  • cam at pos (2, 0, 0)
  • cam dir vector (-1, 0, 0)

That being said, you can accomplish the same by letting the camera be at a fixed position and rotate the object the camera is looking to.

One simple approach is to generate a pair of quaternions, one for each axis rotation. But the axis on which you want to rotate varies as the object rotates. If the camera rotates up looking down to the object center the quaternions should be defined as a rotation of x degrees around the right axis (1, 0, 0) so that the camera rotates up. Once the object has rotated to make the camera look like it is up, the object "up" vector is no longer the global up but the "global up vector" rotated by the previos quaternion.

Approach:

1- For each frame compute the local up and right vector by rotating the global up and right vector using the current object rotation.

2- Once you have the local up and right vector, read the joystick translation amounts.

3- Create two new quaternions that rotate around the local vectors. Multiply both quaternions and the result should be applied (multiplied) with the current object quaternion.

4- Convert that final quaternion to a matrix and use it as your current modelview matrix.

EDIT:

Some code using ShadingZen classes (I don't use libGDX):

class Camera{
static Vector3 mRight = new Vector3(1.f, 0.f, 0.f);
static Vector3 mUp = new Vector3(0.f, 1.f, 0.f);
static Vector3 mFront = new Vector3(0.f, 0.f, 1.f); // Maybe your front is (0, 0, -1) or (0, 1, 0)

Vector3 mLocalRight; = new Vector3(1.f, 0.f, 0.f);
Vector3 mLocalUp = new Vector3(0.f, 1.f, 0.f);
Vector3 mLocalFront = new Vector3(0.f, 0.f, 1.f);

Quaternion mRotation = new Quaternion(0.f, 0.f, 0.f, 1.f); // Identity rotation
Vector3 mCameraInitialPos = ...

...

/**
 * Compute local axis vectors given the current camera rotation 
 * tickDeltaTime is useful to prevent "jumps" in movement 

*/
private void updateRotation(){
    // Get local (rotated) vectors (current local axis)
    mLocalRight = mRotation.mul(mRight); // Rotate mRight using mRotation quaternion
    mLocalUp = mRotation.mul(mUp); 
    mLocalFront = mRotation.mul(mFront);

    Quaternion rotationAroundRightAxis = new Quaternion(mLocalRight, mJoystickAmmountY*tickDeltaTime);
    Quaternion rotationAroundUpAxis = new Quaternion(mLocalUp, mJoystickAmmountX*tickDeltaTime);

    // Chain rotations
    mRotation = mRotation.mul(rotationAroundRightAxis.mul(rotationAroundUpAxis));

    // Now mRotation contains this step or tick's rotation ammount nad past rotations
    mCameraPos = mRotation.mul(mCameraInitialPos);
}

public void update(boolean updateFrustum)
{
    updateRotation();

    float aspect = camera.viewportWidth / camera.viewportHeight;
    camera.projection.setToProjection(Math.abs(camera.near), Math.abs(camera.far), camera.fieldOfView, aspect);
    camera.view.setToLookAt(mCameraPos, mLocalFront, mLocalUp); // This probably computes your view matrix

    // Not sure if libGDX needs this or calculates internally
    camera.combined.set(camera.projection);
    Matrix4.mul(camera.combined.val, camera.view.val);

    if (updateFrustum)
    {
        camera.invProjectionView.set(camera.combined);
        Matrix4.inv(camera.invProjectionView.val);
        camera.frustum.update(camera.invProjectionView);
    }
}
}

I'll give you some tips to think about. May be not a complete answer, but it should help!

1- Is it really necessary to have a mathematically perfect stick? I mean, you can just detect displacement from center, and calculate your angles from that. Eg . Stick is centered at [x,y], user finger is at [x+a, y+b], then your rotation angles could be func(a), func(b), where func() is a function you define (almost always empirically). For instance:

   angleX = (a/30) % 360; //you should consider maximum displacements may be

2- Be careful with how you use your quaternion,

rotation.mul(newRotation); 
rotation.leftMul(newRotation);

are not equivalent. Generally, you use the second option to rotate a rotated thing by the axis you specify, no matter if that model is already rotated. And that's what you want to do, if user moves stick up, you rotate your camera up (you don't care if the camera has accumulated rotations).

3- To complete my "cheap implementation" answer, you could calculate your quaterion really easily using Libgdx's setEulerAngles(float yaw, float pitch, float roll). So:

   newRotation.setEulerAngles(angleX, angleY, 0). 

Remember to leftMultiply this quaterion so you get rotation's on all three axis!

There may be several details missing. May be you should switch parameters in the setEulerAngles, may be your cam will not look at the right place, but i just wanted to show that sometimes a cheap solution is the best solution (as it is a 3 liner approximation). At least should be good enough for a quick prototype!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM