2
\$\begingroup\$

This question already has an answer here:

I'm making a 3D game where the player can walk across the surface of a planet.

I am having a problem correctly rotating the camera's frame of reference, so that the camera is oriented correctly anywhere on the planet.

My strategy so far has been to create a quaternion that represents the rotation from an "up" vector (i.e. north pole) to the current position vector of the camera:

var up = new THREE.Vector3(0, 1, 0); camera.quaternion.setFromUnitVectors(up, camera.position.clone().normalize());

To get the final rotation of the camera, I apply the rotation of where the player is looking:

camera.quaternion.multiply(sensor.getState().orientation);

This means that wherever the player is, if they look down they will look towards the center of the planet.

I then apply that rotation to the up vector to get the direction the player is looking:

var direction = up.clone().applyQuaternion(camera.quaternion);

Assuming the player is looking directly forward, if the player travels in that direction, I would expect them to walk around the planet in a straight line.

This works correctly near the north pole, however as I approach the south pole, the camera seems to rotate laterally (from the perspective of the player), finally as I reach the south pole the camera rapidly rotates 360 degrees around the pole.

What is causing this and how can I avoid it?

\$\endgroup\$

marked as duplicate by DMGregory, Alexandre Vaillancourt, Anko, Seth Battin, Josh Oct 31 '16 at 16:33

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

1
\$\begingroup\$

Your problem seems to be that you're telling your camera that its up direction is a fixed world up, and then turning it upside down, which causes all sorts of fun mathematical chaos that's not at all what you want.

As a fix, try using the direction from the center of the planet to the player as the up on your camera instead of an arbitrary world vector. This should be updated as the player walks around the planet, but if your code was working around the north pole before it should otherwise require no additional changes.

\$\endgroup\$
0
\$\begingroup\$

I haven't worked in Three.js; however, since the problem may be reduced to geometric rotations in mathematics, I'll approach it from that avenue.

Mathematical Approach

From your description, as your azimuthal/inclination/elevation angle approaches the south pole (180°, π rads), the rotation being applied to the camera's polar angle (θ) increases without bound. Is this correct?

If so, then I suspect that the rotation you're applying is multiplying the polar angle by the azimuthal angle, θ → θ x φ.

\$\endgroup\$
  • \$\begingroup\$ Probably best to ask questions in the comments. A question inside an answer does not seem like the best quality choice. \$\endgroup\$ – Gnemlock Oct 6 '16 at 0:31
  • \$\begingroup\$ You're correct. When I had originally posted this, I didn't know how to add a comment. I'll adjust it accordingly @gnemlock. (Thanks!) \$\endgroup\$ – KareemElashmawy Oct 6 '16 at 17:02
0
\$\begingroup\$

I finally solved the problem. Big thanks to @DMGregory and his answer to a similar problem.

His answer is very comprehensive in explaining the problem, but I couldn't get the solution to work with three.js, and it was too prescriptive in the way the player moved.

He points out that this is caused by Hairy Ball Theorem which I understand to mean that you can't have a smooth continuous mapping around a sphere; something I was attempting to do with a quaternion representing the rotation of the player from the north pole.

The solution I used was to incrementally update a quaternion (playerRotation) as the player moves. Each incremental step happens as if the player was at the north pole, avoiding all hairy ball issues:

var playerRotation = new THREE.Quaternion();

function renderLoop() {
  camera.oldPosition = camera.position.clone();

  // Update the camera's position however you want here

  var currentRotation = new THREE.Quaternion();
  currentRotation.setFromUnitVectors(
    camera.oldPosition.clone().normalize(), 
    camera.position.clone().normalize()
  );

  playerRotation.premultiply(currentRotation);

  camera.rotation.copy(playerRotation);
  camera.rotation.multiply(sensor.getState().orientation);
}

One gotcha that stumped me for a while was the fact that quaternion multiplication is not commutative - order matters.

The premultiply step is equivalent toplayerRotation.copy(currentRotation.multiply(playerRotation)) i.e. multiplying the quaternions in reverse order. This is important because playerRotation is a rotation in the world's frame of reference. It needs to be applied before the other rotations.

In contrast sensor.getState().orientation represents the camera rotating around in the player's frame of reference, so that should be applied after playerRotation.

\$\endgroup\$
  • \$\begingroup\$ Thank you so much @DMGregory! I couldn't have solved this without your awesome answer. \$\endgroup\$ – peterjwest Oct 10 '16 at 17:34

Not the answer you're looking for? Browse other questions tagged or ask your own question.