Search Unity

How to virtually grab an object and move

Discussion in 'Scripting' started by skarwild9, May 25, 2016.

  1. skarwild9

    skarwild9

    Joined:
    Mar 11, 2016
    Posts:
    6
    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:

    Code (CSharp):
    1. if(Input.touchCount == 1 && controller.isGrounded)
    2.         {
    3.             if (Input.touches[0].phase == TouchPhase.Began)
    4.             {
    5.                 Ray ray = Camera.main.ScreenPointToRay(Input.touches[0].position);
    6.                 if (Physics.Raycast(ray, out hit))
    7.                 {
    8.                     move = true;
    9.                     angles = transform.eulerAngles;
    10.                     touch1 = Input.touches[1];
    11.  
    12.                 }
    13.             }
    14.             else if (Input.touches[0].phase == TouchPhase.Moved && move)
    15.             {
    16.                
    17.                 Vector3 test = Camera.main.ScreenToWorldPoint(new Vector3(Input.touches[0].position.x, Input.touches[0].position.y, hit.distance));
    18.  
    19.  
    20.                  if (move)
    21.                  {
    22.  
    23.                     Vector3 angles2 = transform.eulerAngles;
    24.                      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;
    25.                      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;
    26.                     Debug.Log("Angle " + Mathf.Rad2Deg * Mathf.Atan((hit.point.y - test.y) / (hit.distance)));
    27.                     angles2.x = angles.x - Mathf.Rad2Deg * Mathf.Atan((hit.point.y - test.y) / (hit.distance);
    Any idea how to do this ?
     
  2. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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:
    http://docs.unity3d.com/ScriptReference/Camera.WorldToScreenPoint.html

    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.
     
  3. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    the amount of rotation is going to depend on the touch's distance from the rotation point...
     
  4. skarwild9

    skarwild9

    Joined:
    Mar 11, 2016
    Posts:
    6


    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
     
  5. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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.
     
  6. skarwild9

    skarwild9

    Joined:
    Mar 11, 2016
    Posts:
    6
    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.
     
  7. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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?
     
    Last edited: May 26, 2016
  8. skarwild9

    skarwild9

    Joined:
    Mar 11, 2016
    Posts:
    6
    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 ?
     
  9. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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.
     
  10. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    I've experimented the idea with the following implementation:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class DragCameraRotation : MonoBehaviour
    4. {
    5.     Vector3 mouseDownPosition;
    6.     Quaternion mouseDownRotation;
    7.  
    8.     new Camera camera;
    9.     // Use this for initialization
    10.     void Start ()
    11.     {
    12.         camera = this.GetComponent<Camera>();
    13.     }
    14.  
    15.     // Update is called once per frame
    16.     void Update ()
    17.     {
    18.         if (Input.GetMouseButtonDown(0))
    19.         {
    20.             mouseDownPosition = Input.mousePosition;
    21.             mouseDownRotation = camera.transform.rotation;
    22.         }
    23.  
    24.         if (Input.GetMouseButton(0))
    25.         {
    26.             float yfov = camera.fieldOfView;
    27.             float xfov = camera.fieldOfView*camera.aspect;
    28.  
    29.             Vector3 displacement = Input.mousePosition - mouseDownPosition;
    30.             Vector2 fraction = Vector2.zero;
    31.             fraction.x = displacement.x / Screen.width;
    32.             fraction.y = displacement.y / Screen.height;
    33.  
    34.             float xangle = fraction.x * xfov;
    35.             float yangle = fraction.y * yfov;
    36.  
    37.             var xrot = Quaternion.AngleAxis(-xangle, camera.transform.up);
    38.             var yrot = Quaternion.AngleAxis(yangle, camera.transform.right);
    39.  
    40.             camera.transform.rotation = yrot* xrot * mouseDownRotation;
    41.  
    42.             // avoid the camera being tilted.
    43.             var e = camera.transform.rotation.eulerAngles;
    44.             camera.transform.rotation = Quaternion.Euler(e.x, e.y, 0.0f);
    45.  
    46.         }
    47.     }
    48. }
    49.  
    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... :)
     
    Last edited: May 28, 2016
  11. skarwild9

    skarwild9

    Joined:
    Mar 11, 2016
    Posts:
    6
    Thanks for you answer, I had the same problem with perspective projection. I will post the solution if I find one x)
     
  12. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    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.
     
    Last edited: May 30, 2016
  13. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    @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.

    Code (CSharp):
    1.             float xangle = fraction.x * xfov;
    2.             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.
     
    Last edited: May 30, 2016
  14. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    Here is another approach:
    1. Get the direction where the mouse is pointing (in world space) at drag start.
    2. Get the direction where the mouse is pointing (in world space) at drag end.
    3. Compute the rotation between these two vectors.
    4. Use that rotation to rotate the camera.
    Here is an implementation to try out this idea.
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class DragCameraRotation : MonoBehaviour
    4. {
    5.     Vector3 directionMouseDown;
    6.     Quaternion mouseDownRotation;
    7.     new Camera camera;
    8.  
    9.     void Start()
    10.     {
    11.         camera = this.GetComponent<Camera>();
    12.     }
    13.  
    14.     // The direction the mouse cursor is pointing in world space.
    15.     Vector3 mouseDirection
    16.     {
    17.         get
    18.         {
    19.             var mp = Input.mousePosition;
    20.             mp.z = camera.farClipPlane;
    21.             var directionWorld = (camera.ScreenToWorldPoint(mp) - camera.transform.position).normalized;
    22.             return directionWorld;
    23.         }
    24.     }
    25.  
    26.  
    27.     // Update is called once per frame
    28.     void Update()
    29.     {
    30.         if (Input.GetMouseButtonDown(0))
    31.         {
    32.             directionMouseDown = mouseDirection;
    33.             mouseDownRotation = camera.transform.rotation;
    34.         }
    35.  
    36.         if (Input.GetMouseButton(0))
    37.         {
    38.             camera.transform.rotation = mouseDownRotation;
    39.             var deltaRotation = Quaternion.Inverse( Quaternion.FromToRotation(directionMouseDown, mouseDirection));
    40.             camera.transform.rotation = deltaRotation * mouseDownRotation;
    41.         }
    42.     }
    43. }
    44.  
    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.
     
    Last edited: May 31, 2016
    skarwild9 likes this.