0

I have a stationary cube in my scene that I'm orbiting a camera around. I have my MainCamera nested under a GameObject that I'm calling 'OrbitalCamera'.

I setup the script so that a click (or tap) and drag will rotate the camera around the object in space so it feels like you're rotating the cube (ie: if I click the top of a face on the cube, and pull down, I'm rotating the X value) but you'll actually be rotating the camera.

For the most part, my script works. However, after rotating the Y so much, the camera is upside down and the X gets inverted. Here's my script:

public class OrbitalCamera : MonoBehaviour {
    public bool cameraEnabled;

    [SerializeField] private float touchSensitivity;
    [SerializeField] private float scrollSensitivity;
    [SerializeField] private float orbitDampening;

    protected Transform xFormCamera;
    protected Transform xFormParent;
    protected Vector3 localRotation;
    protected float cameraDistance;

    void Start () {
        cameraEnabled = true;
        xFormCamera = transform;
        xFormParent = transform.parent;
        cameraDistance = transform.position.z * -1;
    }

    void LateUpdate () {
    if (cameraEnabled) {
            // TODO:: FIX PROBLEM WHERE WHEN CAMERA IS ROTATED TO BE UPSIDEDOWN, CONTROLS GET INVERSED
            if (Input.GetMouseButton(0)) {
                if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0) {
                    localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
                    localRotation.y -= Input.GetAxis("Mouse Y") * touchSensitivity;
                }
            }
        }

        Quaternion qt = Quaternion.Euler(localRotation.y, localRotation.x, 0);
        xFormParent.rotation = Quaternion.Lerp(xFormParent.rotation, qt, Time.deltaTime * orbitDampening);
    }
}

Is there a good method to achieve this type of 360 camera? I'd like dragging from right to left to always move the camera left and dragging left to right to always move the camera right -- no matter how the camera is oriented.

brandoncluff
  • 303
  • 1
  • 5
  • 19
  • I think you're suffering of gimbal lock. – Gusman Jan 31 '18 at 19:50
  • Maybe I'm oversimplifying, but would you not just - = the x axis rotation once the y axis exceeds a specified value? – Joe Feb 01 '18 at 05:09
  • I think there is a more appropriate way to solve this. I think I might need to rotate the cube instead of the camera. I researched "gimbal lock" and I do believe that is what's happening here. – brandoncluff Feb 05 '18 at 17:07

2 Answers2

1

Perhaps you could clamp the above/below pan at 89 degrees?

I recently helped a friend make a mouse gimbal, and found allowing freedom beyond 89 degrees was problematic and unnecessary. It seems like your application is the same, at least for one of the two planes.

In your LateUpdate() call, you could perhaps add:

localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
localRotation.x = Clamp(localRotation.x);

Then, of course, create your clamp function, which should be fairly straight forward:

float Clamp(float val) // prevent values from ~90 - ~270
{
   int lowVal = 89;
   int highVal = 271;
   int midVal = 180;

   if (val > lowVal & val < highVal)
   {
      if (val > midVal) val = highVal;
      else val = lowVal;
   }
return val;
}

A slightly different application, but I'm sure you can see how I've set this up: I apply rotation in two steps. Step 1 - a simple Rotate() call, Step 2 - clamping some/all of the rotation:

using UnityEngine;

public class MouseGimbal : MonoBehaviour
{
   [SerializeField] [Range(0,89)] float maxRotationDegrees = 10.0f; // At 90+ gimbal oddities must be dealt with.
   [SerializeField] bool ClampToMaxRotationDegrees = true; // Disable for free rotation.
   [SerializeField] float rotationSpeed = 10.0f;

   const float fullArc = 360.0f;
   const float halfArc = 180.0f;
   const float nullArc = 0.0f;

   void Update () { Tilt(); }

   void Tilt()
   {
      // Apply the 'pre-clamp' rotation (rotation-Z and rotation-X from X & Y of mouse, respectively).
      if (maxRotationDegrees > 0) { SimpleRotation(GetMouseInput()); }

      // Clamp rotation to maxRotationDegrees.
      if (ClampToMaxRotationDegrees) { ClampRotation(transform.rotation.eulerAngles); }
   }

   void ClampRotation(Vector3 tempEulers)
   {
      tempEulers.x = ClampPlane(tempEulers.x);
      tempEulers.z = ClampPlane(tempEulers.z);
      tempEulers.y = nullArc; // ClampPlane(tempEulers.y); // *See GIST note below...
      transform.rotation = Quaternion.Euler(tempEulers);
      ///Debug.Log(tempEulers);
   }

   float ClampPlane(float plane)
   {
      if (OkayLow(plane) || OkayHigh(plane)) DoNothing(); // Plane 'in range'.
      else if (BadLow(plane)) plane = Mathf.Clamp(plane, nullArc, maxRotationDegrees);
      else if (BadHigh(plane)) plane = Mathf.Clamp(plane, fullArc - maxRotationDegrees, fullArc);
      else Debug.LogWarning("WARN: invalid plane condition");
      return plane;
   }

   Vector2 GetMouseInput()
   {
      Vector2 mouseXY;
      mouseXY.x = -Input.GetAxis("Mouse X"); // MouseX -> rotZ.
      mouseXY.y = Input.GetAxis("Mouse Y"); // MouseY -> rotX.
      return mouseXY;
   }

   void SimpleRotation(Vector2 mouseXY)
   {
      Vector3 rotation = Vector3.zero;
      rotation.x = mouseXY.y * Time.deltaTime * rotationSpeed;
      rotation.z = mouseXY.x * Time.deltaTime * rotationSpeed;
      transform.Rotate(rotation, Space.Self); 
   }

   void DoNothing()           {   }
   bool OkayHigh(float test)  { return (test >= fullArc - maxRotationDegrees && test <= fullArc); }
   bool OkayLow(float test)   { return (test >= nullArc && test <= maxRotationDegrees); }
   bool BadHigh(float test)   { return (test > halfArc && !OkayHigh(test)); }
   bool BadLow(float test)    { return (test < halfArc && !OkayLow(test)); }
}
Jack Draak
  • 11
  • 4
0

When I developed an orbital camera, I created 3 objects:

- x_axis (rotate with vertical mouse moviment)
    - y_axis (rotate with horizontal mouse moviment)
        - camera (look_at object) (translated Vector3(0,0,-10))