Search Unity

  1. Unity 2019.1 is now released.
    Dismiss Notice

ScreenToWorldPoint doesn't return constant output given constant input

Discussion in 'Scripting' started by alex_dossamer, Nov 6, 2018.

  1. alex_dossamer

    alex_dossamer

    Joined:
    Jun 19, 2018
    Posts:
    3
    See the following code. For the purposes of this example, know that delta holds constant frame-to-frame.

    Code (CSharp):
    1. Vector3 delta = Input.mousePosition - scrollStartPosition;
    2.  
    3. if (delta.magnitude > precision)
    4. {
    5.    Vector3 transformDelta = referenceCamera.ScreenToWorldPoint(new Vector3(delta.x, delta.y, 10));
    6.  
    7.     Debug.Log(transformDelta + ", " + delta);
    8. }
    Running this code will output the following, in succession, oscillating:

    Code (CSharp):
    1. (-2.8, -0.6, 0.0), (0.0, 263.0, 0.0)
    2. (-2.8, 1.7, 0.0), (0.0, 263.0, 0.0)
    3. (-2.8, -0.6, 0.0), (0.0, 263.0, 0.0)
    4. (-2.8, 1.7, 0.0), (0.0, 263.0, 0.0)
    transformDelta is the set on the left,
    delta is the set on the right.

    Curiously, only the y value oscillates.

    This variability in ScreenToWorldPoint makes my code unusable.

    Any thoughts on what could be happening? Thanks!
     
  2. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,077
    Missing code filled in (guessed) so it's actually runnable.
    No errors encountered from the code you posted, output reads
    (-8.2, -4.9, 0.0), (12.0, -6.0, 0.0)
    (-8.2, -4.9, 0.0), (12.0, -6.0, 0.0)
    (-8.2, -4.9, 0.0), (12.0, -6.0, 0.0)
    (-8.2, -4.9, 0.0), (12.0, -6.0, 0.0)

    Code (CSharp):
    1. public class TestScript: MonoBehaviour
    2. {
    3.    Vector3 scrollStartPosition;
    4.    public Camera referenceCamera;
    5.    float precision = 1f;
    6.    private void Update()
    7.    {
    8.        if (Input.GetMouseButtonDown(0))
    9.        {
    10.            scrollStartPosition = Input.mousePosition;
    11.        }
    12.  
    13.        if (Input.GetMouseButton(0))
    14.        {
    15.            Vector3 delta = Input.mousePosition - scrollStartPosition;
    16.            if (delta.magnitude > precision)
    17.            {
    18.                Vector3 transformDelta = referenceCamera.ScreenToWorldPoint(new Vector3(delta.x, delta.y, 10));
    19.                ....
    20.                Debug.Log(transformDelta + ", " + delta);
    21.            }
    22.        }
    23.    }
    24. }
    My guess is you are doing something else, perhaps around line 17 where I have put in ..., maybe you are moving the camera by the calculated delta
     
  3. Lurking-Ninja

    Lurking-Ninja

    Joined:
    Jan 20, 2015
    Posts:
    3,414
    It does not change for me unless I move my mouse. Try it without the Input, maybe your mouse or the driver of the mouse is crappy and just feeds Unity with some changing data.
     
    Joe-Censored likes this.
  4. alex_dossamer

    alex_dossamer

    Joined:
    Jun 19, 2018
    Posts:
    3
    That was exactly it! I was using the code to scroll the camera. I was using ScreenToWorldPoint to convert scroll magnitude from screenspace units to worldspace units, but wasn't accounting for the fact that although the screenspace origin is fixed, the worldspace origin relative to the camera isn't--so if I move the camera and try to project the same screen coords into the world they'll project differently depending on where I moved the camera.

    The vector of the original delta, if you think of it as a line segment with endpoints, can be calculated from two points (vectors) in screenspace: (x, y) - (0, 0)

    In my original solution, I was transforming just one of those points, (x, y), to worldspace, and throwing away the other one, meaning I was getting a totally incorrect final vector. To get the correct vector, I need to convert both points involved in the calculation: ScreenToWorld(x, y) - ScreenToWorld(0, 0)

    You would think I could just do ScreenToWorld( (x, y) - (0, 0) ),
    but that just puts us back at square one with ScreenToWorld(x, y), which we've already shown is not equivalent to STW(x, y) - STW(0, 0). (My error was assuming the opposite.)

    To hammer it home, the really big takeaway is:

    In screenspace:
    (x, y) == (x, y) - (0, 0)
    (x, y) - (x, y) == (0, 0)

    But converting to worldspace:
    STW(x, y) != STW(x, y) - STW(0, 0)
    STW(x, y) - STW(x, y) != STW(0, 0)

    STW(x, y) - STW(x, y) == (0, 0)
    STW(0, 0) != (0, 0)

    In other words, the origin in screenspace does not transform to the origin in worldspace. Which means that identities in screenspace { (x, y) == (x, y) - (0, 0) } will not hold when transformed to world space { STW(x, y) != STW(x, y) - STW(0, 0) }. So your calculations between the two spaces cannot rely on those identities.

    Anyways, after taking all that into account, the final behavior is as expected: transformDelta holds constant frame-to-frame so long as delta holds constant.

    Here was my final code:

    Code (CSharp):
    1. Vector3 point = referenceCamera.ScreenToWorldPoint(new Vector3(delta.x, delta.y, clipDistance));
    2. Vector3 origin = referenceCamera.ScreenToWorldPoint(new Vector3(0, 0, clipDistance));
    3.  
    4. Vector3 transformDelta = point - origin;
    5.  
    6. Debug.Log(transformDelta + ", " + delta);
    7.  
    8. float x, y;
    9.  
    10. x = isXAxisEnabled ? transformDelta.x : 0f;
    11. y = isYAxisEnabled ? transformDelta.y : 0f;
    12.  
    13. transform.position = startPosition - new Vector3(x, y, 0f);
    Thank you so much for your help! It was a simple but not-so-simple oversight that threw me off, and you made it click for me.

    (Apologies for all my questionable pseudo-math--my understanding of all this is more intuitive than it is formal.)
     
  5. hpjohn

    hpjohn

    Joined:
    Aug 14, 2012
    Posts:
    2,077
    Uhhm.
    Slight wall'o'text but some things you might need to consider
    ScreenToWorldPoint - the center of the screen is not (0,0) - thats the bottom left corner; but (camera.pixelWidth/2, camera.pixelHeight/2)
    Also, if you are supplying the camera min clip distance as the z property, thats never going to be the origin of the camera, but some point infront of it.
    If your code works now, you probably don't need to worry