Quaternions!

Greetings!
I’m trying to come up with a camera controller which would make a camera fully rotate around an object. The camera controller is semi-functional, since the problem it runs into is the controls getting inverted after you go beyond 180 degrees & it spinning around the x axis when it hits 90 degrees.
Does anyone have a better solution to this problem or maybe has the necessary “If” statement to differentiate the angles?

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public class Level1_CameraController : MonoBehaviour {

    public Camera cam;

    float rotationX = 0F;
    float rotationY = 0F;

    public float sensitivityY = 1F;
    public float sensitivityX = 1F;
    public float targetRadius = 3.5F;
    public float orthoZoomSpeed = 1F;
    public float perspectiveZoomSpeed = 1F;

    void Start() {
        GetComponent<Camera>().fieldOfView = 60F;
    }

    void Update() {
       
        TouchtoRotate();

        Vector3 rot = transform.rotation.eulerAngles;
        Vector3 dir = Quaternion.Euler(rot) * Vector3.back * targetRadius;

        transform.position = dir;
    }

    void TouchtoRotate() {

        if (Input.touchCount > 0) {

            rotationX -= Input.GetTouch(0).deltaPosition.x;
            rotationY -= Input.GetTouch(0).deltaPosition.y;

            Quaternion xQuaternion = Quaternion.AngleAxis(rotationX * sensitivityX, Vector3.down);
            Quaternion yQuaternion = Quaternion.AngleAxis(rotationY * sensitivityY, Vector3.right);

            transform.localRotation = Quaternion.identity * xQuaternion * yQuaternion;
        }
    }

I’m wondering why this?

Vector3 rot = transform.rotation.eulerAngles;
Vector3 dir = Quaternion.Euler(rot) * Vector3.back * targetRadius;

Note, that ‘transform.rotation’ returns a quaternion. Yet you convert it to eulerAngles, then back to a quaternion. Why the extra work to get the same exact result (well, not the same exact, since float error will knock the quaternion off ever so slightly… but barely noticeable).

Or this one:

transform.localRotation=Quaternion.identity* xQuaternion * yQuaternion;

That’s like saying 1 * x * y. Quaternion.identity is the identity value, fulfilling the identity theorem (a function for which the input is the output… f(x) = x * identity = x). It’s why it’s called Quaternion.identity.

As for your flipping direction after it rotates 180 degrees. Running it on my machine with mouse input instead of touch input results in no such issue.

Unless you mean when you rotate it upside down?

In which case, yeah… that happens.

Thing is it also rotates weird when you look down from above. Where sliding left or right makes your rotate around.

So really, what is the expected behaviour at all these positions? I’d think you’d want some more like relative rotation… where you rotate around the current relative up vector of the camera for left/right sliding… but that’s what I would expect. What do YOU expect?

Obviously, I am relatively new to Quaternions so some things may or may seem ridiculous, I apologise. However, the first line was my interpretation of locking onto the object [which is a sphere] in my project.

Vector3 rot = transform.rotation.eulerAngles;
Vector3 dir = Quaternion.Euler(rot) * Vector3.back * targetRadius;

I do agree there are better ways to do it, but this is the only one I found that did not run into an error and worked.

Also, the desired behaviour is that the player uses the touch input to rotate around the object on his screen, which should be thought as x and y axes [2D plane] , yet the camera rotates always upwards, downwards & left or right, despite the fact that the actual Axes [x, y & z] are stationary. Essentially, the purpose is to make so that the input would always rotate the camera around the object in only these 4 directions in 3D space no matter the camera’s position.

Do:

Vector3 dir = transform.rotation * Vector3.back * targetRadius;

Same exact result.

So you mean, relative to the current camera orientation?

Where may the camera be facing down from above, a swipe left would result with a rotation that looks as if it were oriented in front of it?

1 Like
Vector3 dir = transform.rotation * Vector3.back * targetRadius;

Thank you it works just the same, yet more efficient.

And yes! What I was trying to rephrase, is the idea to make it so it looks that the camera is always in front of the object and the swipes would result in the same exact optical illusion.

Try something like this…

note, it’ll probably need tweaking, since full rotation on the surface of a sphere can get very disorienting when you flip upside down and what not. For example when gathering the delta, you can soften the delta by setting either value (x or y) to 0 if it’s to minor. So that way when the person swipes say up, they’ll probably end up with a slight left/right motion they didn’t intend. So for slight values you 0 them out.

I’ve also included a way to quickly orient back to up is up from the current rotation. It can be very jarring… it’s just useful for getting your bearings again. As I said… full spherical 3d rotation is disorienting to the average human. Especially since most scenes have various visual things to orient by (like the skybox has a horizon) which makes it feel like your flipping on your head.

Furthermore, because I don’t have touch, I abstracted the ‘inputs’ to a class that can swap between using mouse or touch inputs.

using UnityEngine;

public class Level1_CameraController : MonoBehaviour
{

    public Camera cam;
  
    public float sensitivityY = 1F;
    public float sensitivityX = 1F;
    public float targetRadius = 3.5F;
    public float orthoZoomSpeed = 1F;
    public float perspectiveZoomSpeed = 1F;


    private SwipeController _swipeController;

    void Start()
    {
        _swipeController = new SwipeController();
        _swipeController.Init(false); //I'm using mouse

        GetComponent<Camera>().fieldOfView = 60F;
        this.UpdatePosition();
    }

    void Update()
    {
        _swipeController.Update();

        TouchtoRotate();
        UpdatePosition();

        if (Input.GetMouseButtonDown(1))
        {
            this.OrientUpward();
        }

    }

    void TouchtoRotate()
    {
        if (_swipeController.IsClicking)
        {
            var dp = _swipeController.Delta;
          
            Quaternion horRot = Quaternion.AngleAxis(dp.x * sensitivityX, Vector3.down);
            Quaternion verRot = Quaternion.AngleAxis(dp.y * sensitivityY, Vector3.right);

            transform.localRotation *= horRot * verRot;
        }
    }

    void UpdatePosition()
    {
        transform.position = transform.rotation * Vector3.back * targetRadius;
    }

    /// <summary>
    /// Naive rotation to orient camera to up is up from the current rotation.
    /// </summary>
    public void OrientUpward()
    {
        var lookDir = transform.rotation * Vector3.forward;
        transform.rotation = Quaternion.LookRotation(lookDir, Vector3.up);
        this.UpdatePosition();
    }




    /// <summary>
    /// Allows swapping between mouse/touch input
    /// </summary>
    public class SwipeController
    {
      
        private Vector3 _lastMousePosition;

        public bool UseTouchControls
        {
            get;
            set;
        }

        public bool IsClicking
        {
            get { return (this.UseTouchControls) ? Input.touchCount > 0 : Input.GetMouseButton(0); }
        }

        public Vector2 Delta
        {
            get;
            private set;
        }


        public void Init(bool useTouchControls)
        {
            _lastMousePosition = Input.mousePosition;
            this.UseTouchControls = useTouchControls;
        }

        public void Update()
        {
            if(this.UseTouchControls)
            {
                this.Delta = Input.GetTouch(0).deltaPosition;
            }
            else
            {
                this.Delta = (Input.mousePosition - _lastMousePosition) * 0.2f; //we dampen the mouse motion...
            }
            _lastMousePosition = Input.mousePosition;
        }


    }

}
1 Like

Alright, so this works amazing and is Exactly what I needed on PC.
However, on touchscreen it keeps snapping on every click to a new location from which you can rotate around so to anyone in the future that may stumble upon this whilst looking for a solution to a similar problem I advise to use this code, but to really examine it and include it based on your needs.
Thank you, lordofduct~

It might keep snapping due to the line:

if(Input.GetMouseButtonDown(1))
{
    this.OrientUpward();
}

Not sure if touch screen forwards right clicks as some gesture or something. Try removing that line of code… I really only included it as a way to reset the orientation.

Actually it appears to have no effect weather you keep it or not.
It’s just that at first if you use this code straight forward, the check for android platform is left out. Input this line and it works perfect.

_swipeController.Init(Application.platform == RuntimePlatform.Android ? true : false);

oh, yeah, you were using the mouse code on android.

Yeah, that’s why I left the comment that I passed in ‘false’ because I was using mouse.