How to virtually grab an object and move

Hi all
I have a big problem, I’m trying to implement a mobile control.
I would to touch the screen, if I touch an object (a cube in my example), I would like to move my finger to rotate the camera .
This idea is easy to code but I also would like to rotate the camera at the same speed of the finger movment to always have the finger at the same point on the object.
It’s pretty difficult because the object are not on the same plane…

My code:

if(Input.touchCount == 1 && controller.isGrounded)
        {
            if (Input.touches[0].phase == TouchPhase.Began)
            {
                Ray ray = Camera.main.ScreenPointToRay(Input.touches[0].position);
                if (Physics.Raycast(ray, out hit))
                {
                    move = true;
                    angles = transform.eulerAngles;
                    touch1 = Input.touches[1];

                }
            }
            else if (Input.touches[0].phase == TouchPhase.Moved && move)
            {
               
                Vector3 test = Camera.main.ScreenToWorldPoint(new Vector3(Input.touches[0].position.x, Input.touches[0].position.y, hit.distance));


                 if (move)
                 {

                    Vector3 angles2 = transform.eulerAngles;
                     angles.y += -(Mathf.Abs(Input.touches[0].position.x - hit.point.x) / (Input.touches[0].position.x - hit.point.x)) * Input.touches[0].deltaPosition.x * 40f * 1f/test.magnitude *Time.fixedTime;
                     angles.x += (Mathf.Abs(Input.touches[0].position.y - hit.point.y) / (Input.touches[0].position.y - hit.point.y)) * Input.touches[0].deltaPosition.y * 40f * 1f/test.magnitude*Time.fixedTime;
                    Debug.Log("Angle " + Mathf.Rad2Deg * Mathf.Atan((hit.point.y - test.y) / (hit.distance)));
                    angles2.x = angles.x - Mathf.Rad2Deg * Mathf.Atan((hit.point.y - test.y) / (hit.distance);

Any idea how to do this ?

So you want the object to stay under one finger while the camera is rotating, that is you want the object to stay at the same location in screen space.

I have an approach, but no complete solution.

You know where the object is in screen space by using:

After the rotation of the camera, the object is no more at the same location in screen space, so we need to translate the camera such that the object is back at the original position.

We know the new position of the object in screen space, therefore we know how much it has been displaced in screen space. We need to somehow use that information to displace the camera in world space… right now, it’s not clear to me how to map the object displacement error to a displacement correction for the camera.

the amount of rotation is going to depend on the touch’s distance from the rotation point…

I just want to rotate the camera, I don’t need to translate it

"
New
the amount of rotation is going to depend on the touch’s distance from the rotation point…

"
Yes but it also depends of the distance between the camera and the touch object

When you rotate the camera, excepted for objects exactly centered on the screen, any other visible objects will have their position changed on the screen, right? However, if I understand your problem correctly, you want to keep a specific object at the same location on the screen (under the finger), therefore you need to somehow translate the camera.

In fact, I don’t need to make any translation.
I have to find the exact ratio between the camera rotation and the finger movment to keep the object under the finger.

Oh! I think I get it.

So, you want to select on object on the screen with a touch down, then while you are moving your finger across the screen, you want the camera to rotate such that the touched object stays under the finger. The overall effect would feel like dragging the touched object.

Do I get this right?

Yes this is exactly what I want. Do you know the name of this mobile interaction or do you have an idea about the implementation ?

You would need to convert the dragging displacement to two angles: one for horizontal rotation, one for vertical rotation.

The camera Field of View angle is vertical: it indicates what angle the height of the screen is covering. Then using the screen aspect ratio you can as well compute the horizontal FoV angle.

Then you can compute the fractions covered by the displacement in both direction. And use these fractions to compute your rotation angles.

Edit:
You don’t even need to know what object is under the finger.

I’ve experimented the idea with the following implementation:

using UnityEngine;

public class DragCameraRotation : MonoBehaviour
{
    Vector3 mouseDownPosition;
    Quaternion mouseDownRotation;

    new Camera camera;
    // Use this for initialization
    void Start ()
    {
        camera = this.GetComponent<Camera>();
    }
 
    // Update is called once per frame
    void Update ()
    {
        if (Input.GetMouseButtonDown(0))
        {
            mouseDownPosition = Input.mousePosition;
            mouseDownRotation = camera.transform.rotation;
        }

        if (Input.GetMouseButton(0))
        {
            float yfov = camera.fieldOfView;
            float xfov = camera.fieldOfView*camera.aspect;

            Vector3 displacement = Input.mousePosition - mouseDownPosition;
            Vector2 fraction = Vector2.zero;
            fraction.x = displacement.x / Screen.width;
            fraction.y = displacement.y / Screen.height;

            float xangle = fraction.x * xfov;
            float yangle = fraction.y * yfov;

            var xrot = Quaternion.AngleAxis(-xangle, camera.transform.up);
            var yrot = Quaternion.AngleAxis(yangle, camera.transform.right);

            camera.transform.rotation = yrot* xrot * mouseDownRotation;

            // avoid the camera being tilted.
            var e = camera.transform.rotation.eulerAngles;
            camera.transform.rotation = Quaternion.Euler(e.x, e.y, 0.0f);

        }
    }
}

But the mouse is indeed not exactly at the same position during the dragging, it is even more noticeable as the FoV angle is large and with long dragging. It seems this issue is related to the perspective projection, I’m not sure though.
It’s more difficult than it seems… :slight_smile:

Thanks for you answer, I had the same problem with perspective projection. I will post the solution if I find one x)

What you want to achieve is simply impossible just by rotating the camera.

Demonstration:
Let’s consider only the vertical movement of the mouse cursor. This movement would make the camera look up and down. If you look at a specific point in the scene, this point will be shifted left or right because of the perspective (unless that point is perfectly centered on the screen). Therefore that point can’t stay under the mouse cursor.

Though, I still think this effect is achievable through both a rotation and a translation. Since you can place any point in the scene wherever on the screen, including exactly under the mouse cursor.

@ericbegue You demonstration is fallacious. If the mouse cursor is constrained on the vertical axis, it does not mean that the camera rotation have to be constrained as well.

Well, that’s still true without a translation.

            float xangle = fraction.x * xfov;
            float yangle = fraction.y * yfov;

Also, that can’t be right. The perspective projection is not a linear function, therefore the function that maps the displacement on the screen to the camera rotation can’t be a linear function.

Here is another approach:

  • Get the direction where the mouse is pointing (in world space) at drag start.
  • Get the direction where the mouse is pointing (in world space) at drag end.
  • Compute the rotation between these two vectors.
  • Use that rotation to rotate the camera.

Here is an implementation to try out this idea.

using UnityEngine;

public class DragCameraRotation : MonoBehaviour
{
    Vector3 directionMouseDown;
    Quaternion mouseDownRotation;
    new Camera camera;

    void Start()
    {
        camera = this.GetComponent<Camera>();
    }

    // The direction the mouse cursor is pointing in world space.
    Vector3 mouseDirection
    {
        get
        {
            var mp = Input.mousePosition;
            mp.z = camera.farClipPlane;
            var directionWorld = (camera.ScreenToWorldPoint(mp) - camera.transform.position).normalized;
            return directionWorld;
        }
    }


    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            directionMouseDown = mouseDirection;
            mouseDownRotation = camera.transform.rotation;
        }

        if (Input.GetMouseButton(0))
        {
            camera.transform.rotation = mouseDownRotation;
            var deltaRotation = Quaternion.Inverse( Quaternion.FromToRotation(directionMouseDown, mouseDirection));
            camera.transform.rotation = deltaRotation * mouseDownRotation;
        }
    }
}

It works. In the sens that you have the feeling to grab the object around, but the camera becomes tilted, which might not be wanted. But it is at least a progression in the right direction.
BTW, thank you @skarwild9 ! Thank you for having tormented me.

1 Like