1
\$\begingroup\$

I currently have a camera which orbits a specific target object, always looking at it.

The user can drag the mouse to move the camera left/right/up/down and the camera will move over the surface of a sphere of fixed radius (note, I clamp the vertical angle to ± 89°).

Currently it looks something like this...

// Store position of camera for future reference
private Vector2 rotation = new Vector2(120, 25);

// Was intended to be the vector from planet to target. Only works with `up`
private rotationalPole = Vector3.up;

if (isDragging) {
    rotation += new Vector2(
        Input.GetAxis("Mouse X") * xSpeed * radius * 0.02f,
        -Input.GetAxis("Mouse Y") * ySpeed * 0.02f);

}

//target is the GameObject to center on...

var pos = target.transform.position;

camObj.transform.position = pos + (Vector3.back * actualRadius);
camObj.transform.LookAt(pos, rotationalPole);
camObj.transform.RotateAround(pos, Vector3.right, rotation.y);
camObj.transform.RotateAround(pos, rotationalPole, rotation.x);

This works fine when up is in the y direction.

Now, however, instead of an object on the ground, the target is something in orbit around a planet (currently on an equatorial orbit but hopefully on an arbitrary one in future).

I want the camera to behave the same as before, but with up being the vector from the center of the planet through the target. Nominally forward will be the direction of orbit (but since the user can spin through 360 "horizontally", that's less important).

Currently it looks like this...

enter image description here

Basically, I'd like to rotate the image above counter-clockwise by 90° (which I believe I can do by playing with the camera transform's up and right vectors) but also that my arbitrary rotation honors the new orientation.

Some research shows that I need to use quaternions but while my mental model for basic trig is fine, I can't picture how to use quaternions correctly in this situation.

Following on from @Jon's answer below...

I've now got the following.

var planetToTarget = (target.transform.position - planet.transform.position).normalized;
// "Reset" the camera before I do any transforms
this.transform.position = target.transform.position - tgtMovement * actualRadius;
this.transform.up = Vector3.up;
this.transform.LookAt(target.transform.position);

var camToTarget = (transform.position - target.transform.position).normalized;
Debug.Log(string.Format("targetMotion = {0}, planetToTarget: {1}, camToTarget: {2}", tgtMovement, planetToTarget, camToTarget));

// Line that's not working...
this.transform.Rotate(camToTarget, -90);

// To be added in when the above is working...  
//this.transform.Rotate(Vector3.Cross(camToTarget, planetToTarget), rotation.y);
//this.transform.Rotate(planetToTarget, rotation.x);
//this.transform.up = planetToTarget;

The uncommented code (excluding the last line) keeps the camera behind the target, looking forward...

Initially, I get :

targetMotion = (0.0, 0.0, -1.0), planetToTarget: (-1.0, 0.0, 0.0), camToTarget: (0.0, 0.0, 1.0)

Which generates:

enter image description here

However, as the target moves around the planet, it changes to:

targetMotion = (0.4, 0.0, -0.9), planetToTarget: (-0.9, 0.0, -0.4), camToTarget: (-0.4, 0.0, 0.9)

By which point, the camera is slewing to the side...

enter image description here

So I'm clearly still missing something. I don't appear to be rotating around camToTarget correctly?

\$\endgroup\$
  • \$\begingroup\$ @Jon RotateAround is deprecated. I had an "oh shit" moment after looking at the docs, but one of the overloads of Rotate() takes a Vector3 and an Angle (Scroll to the last section of this page. The default "Space" (reference frame) is "Self"). So I did get it right (more by luck than judgement). I verified by creating an explicit Quaternion rotation and multiplying it by the camera transform and got identical (slewing) results. That said, thanks for the suggestion... Never rule out stupidity \$\endgroup\$ – Basic Feb 28 '16 at 19:31
  • \$\begingroup\$ Me too; didn't see that last overload. Try using Space.World as your reference. camToTarget is relative to the world, not to Self. \$\endgroup\$ – Jon Feb 28 '16 at 19:32
  • \$\begingroup\$ We have to Scale, then Rotate, then Translate, with matrices because they all operate at (0,0). To revolve something that isn't at (0,0), you have to remove the translation and scale components, rotate, then reapply the missing components. Quats describe twisting around a direction vector, so they aren't really "at" a location (0,0) or otherwise; they operate on all inputs the same. \$\endgroup\$ – Jon Feb 28 '16 at 19:39
  • \$\begingroup\$ @Jon Thanks... That did it. The next steps are broken (of course!) as they're centering the rotation on the camera not the target, but I'll take some time to and try to wokr out how to pass in an arbitrary origin when using a quaternion. Thanks for all your help. Edit: Your comment above just hit the nail on the head... \$\endgroup\$ – Basic Feb 28 '16 at 19:39
  • \$\begingroup\$ Does target.transform.position work for RotateAround(point, ..., ...)? planet.transform.position should work too, because both would define the same line/ray. \$\endgroup\$ – Jon Feb 28 '16 at 19:41
2
\$\begingroup\$

Once you calculate the new up, you'll want the lerp from old to new to take a few seconds. Additionally, if CameraToObject and PlanetToObject are coincident, a single plane cannot be defined. If the object is already selected, your vertical constraint will prevent CameraToObject and PlanetToObject from becoming coincident, however, if the two are already coincident when you click the object, or are made coincident by clicking the object, the algorithm will not work. So, if you go to calculate and detect that they are coincident, bump one a tiny bit; this will find an up vector, which allows the camera to begin lerping, which allows the vertical constraint to work.

The cross-product of the normalized PlanetToObject and CameraToObject directions is perpendicular to both vectors and is, by definition, the normal of a plane that contains both. The cross product of the plane's normal (perpendicular to the plane) and CameraToObject (a vector in the plane) is a second vector in the same plane, perpendicular to the first; the net-result is a rotation of CameraToObject 90 degrees around the face normal.

Note: I may or may not have reversed the order of the cross-products, but you only need to worry about it at the very end; the yellow vector should point to the "right of the camera"; invert it if it doesn't.

This was one of the hardest screenshots I've tried to diagram over; I had given up on making it look right but will post it anyway, since I think you'll still get it.

enter image description here

Regarding quats:

Consider the specific wording of your question. "I want to rotate the image counter-clockwise". That's also saying that you want to rotate the camera clock-wise around CameraToObject. In that case, CameraToObject is the Quaternion's vector component, and "90 degrees" is the scalar component. Quaternions twist inputs around their vector component by the angle stored in their scalar component.

You didn't know you've already been using quaternions for matrix math? With unit-axis vector components, the quaternion equation reduces to the appropriate axis-rotation matrices:

enter image description here

\$\endgroup\$
  • \$\begingroup\$ Wow, that's a really long/detailed answer answer, thank you. I think it covers the things I need, but I'm going to have to do some checking \$\endgroup\$ – Basic Feb 28 '16 at 14:21
  • \$\begingroup\$ To confirm my understanding... I currently store a Vec2 rotation that represents how the user has rotated the camera. Right now, I handle that by placing the camera at (target-<0,0,1>), then rotating around x, then y. In this case, I'd place at (target-NormTargetMovement ("behind" target as it moves)), rotate around the CameraToObject to rotate the camera "up", then around the (PlanetToObject x CameraToObject) vector before finally rotating around PlanetToObject? \$\endgroup\$ – Basic Feb 28 '16 at 14:57
  • \$\begingroup\$ I've edited my question with more information as I'm clearly still missing something. \$\endgroup\$ – Basic Feb 28 '16 at 16:21
  • \$\begingroup\$ @Basic, this is how the computer sees it: Start with camera at (0,0). Move it by <0,0,-1>. You now orbit (0,0) rather than revolve because you're no longer at (0,0). Then, rotate around (0,0) by x, then y. Then, rotate the camera to the correct "up". Since the camera is 1-unit away from (0,0), the axis of rotation is simply its' transform position. At this point, all objects, rotations, and normalized direction vectors are relative to (0,0). The camera is, now, perfectly rotated and, finally, translated into it's final place. This is known as a "Model Viewer" camera. \$\endgroup\$ – Jon Feb 28 '16 at 20:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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