Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

Resolved Recommended/default scale in Windows 10 breaks SceneView mouse position

Discussion in '2020.2 Beta' started by adamgolden, Aug 15, 2020.

  1. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    I'm using 2020.2.0a19.

    I've isolated the cause and have a workaround. If this has been posted/reported before, I apologize.

    In Windows 10, Display Settings:
    win_10_display_settings.jpg

    If you use the Recommended setting of 125% (which it was set to by default), you have to multiply Event.current.mousePosition by 1.25f.

    This is the workaround:
    Code (CSharp):
    1. float windowsScale = 1.0f; // must be 1.25f if using 125%
    2. Vector2 screenPoint = Event.current.mousePosition * windowsScale;
    3. screenPoint.y = sv.camera.pixelHeight - screenPoint.y;
    4.  
    5. // Raycast in SceneView will only hit exactly at mouse position with the above.
    6.  
    7. SceneView sv = SceneView.lastActiveSceneView;
    8. PhysicsScene ps = sv.camera.scene.GetPhysicsScene();
    9. Ray ray = sv.camera.ScreenPointToRay(screenPoint);
    10. if (ps.Raycast(ray.origin, ray.direction, out RaycastHit hit))
    11. {
    12.   Handles.DrawSolidDisc(hit.point, hit.normal, 0.25f);
    13. }
     
  2. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    That's interesting, since my is 100% on a 3440x1440 resolution

    upload_2020-8-17_23-12-2.png

    What resolution/monitor do you use? Must be 4k if they're recommending scaling? Or maybe a laptop screen?

    (regardless this is obviously an issue)
     
  3. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    Laptop, 1080p
     
  4. TheZombieKiller

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    265
    You can use this class to retrieve the current scale factor instead of hard-coding it:
    Code (CSharp):
    1. using System;
    2. using System.Diagnostics;
    3. using System.Runtime.InteropServices;
    4. using UnityEditor;
    5.  
    6. public static class WindowScaleFactor
    7. {
    8.     public static float Current
    9.     {
    10.         get
    11.         {
    12.         #if UNITY_EDITOR_WIN
    13.             return GetDpiForWindow(editorWindow) / 96f;
    14.         #else
    15.             return 1;
    16.         #endif
    17.         }
    18.     }
    19.  
    20. #if UNITY_EDITOR_WIN
    21.     static IntPtr editorWindow;
    22.  
    23.     [DllImport("user32")]
    24.     extern static int GetDpiForWindow(IntPtr hWnd);
    25.  
    26.     [DllImport("user32")]
    27.     extern static int GetWindowThreadProcessId(IntPtr hWnd, out int processId);
    28.  
    29.     [DllImport("user32")]
    30.     [return: MarshalAs(UnmanagedType.Bool)]
    31.     extern static bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    32.  
    33.     [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    34.     [return: MarshalAs(UnmanagedType.Bool)]
    35.     delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
    36.  
    37.     static bool WindowHandleFromProcessId(IntPtr hWnd, IntPtr lParam)
    38.     {
    39.         GetWindowThreadProcessId(hWnd, out int processId);
    40.  
    41.         if (lParam.ToInt32() == processId)
    42.         {
    43.             editorWindow = hWnd;
    44.             return false;
    45.         }
    46.  
    47.         return true;
    48.     }
    49.  
    50.     [InitializeOnLoadMethod]
    51.     static void Initialize()
    52.     {
    53.         EditorApplication.delayCall += () =>
    54.         {
    55.             var process = Process.GetCurrentProcess();
    56.             EnumWindows(WindowHandleFromProcessId, new IntPtr(process.Id));
    57.         };
    58.     }
    59. #endif
    60. }
     
    XGT08 and adamgolden like this.
  5. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
  6. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    camera.ScreenPointToRay - needs the scaling applied to mousePosition
    HandleUtility.GUIPointToWorldRay - does NOT need the scaling applied to mousePosition

    I was about to post that this was only working in some places and not others and I couldn't figure out why.. then took another look and noticed.

    Edit: Note that ScreenPointToRay only has this issue in the Editor with a SceneView camera and Event.current.mousePosition, but it works fine in Play Mode / builds with normal camera and Input.mousePosition.
     
    Last edited: Aug 20, 2020
  7. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    @TheZombieKiller There have been occasions where it would start returning 0.0f at some random time after working in the Editor for a while. It returns the proper value again as soon as you go change scaling in Windows display settings (without reloading the editor or anything). I added a fallback but do you have any idea why that might happen?
    Code (CSharp):
    1. #if UNITY_EDITOR_WIN // line 12
    2.       float windowDPI = GetDpiForWindow(editorWindow) / 96f;
    3.       return (windowDPI != 0) ? windowDPI : 1.0f; // fallback
     
  8. TheZombieKiller

    TheZombieKiller

    Joined:
    Feb 8, 2013
    Posts:
    265
    I think GetDpiForWindow will return zero for an invalid or null window handle, so maybe the editorWindow pointer is becoming invalid somehow? Changing the scaling in Windows display settings will cause an editor reload, which would cause the code to search for the window handle again, so it's a possibility.

    You could use this function to check if it's still a valid window:
    Code (CSharp):
    1. [DllImport("user32")]
    2. [return: MarshalAs(UnmanagedType.Bool)]
    3. extern static bool IsWindow(IntPtr hWnd);
    And then call Initialize() if it returns false. You can also try just calling that and re-attempting to get the DPI when you get zero.

    EDIT: Scratch that, since Initialize uses delayCall, editorWindow wouldn't be set immediately after. Calling EnumWindows with the appropriate callback and process id instead is what should be done.
     
    Last edited: Aug 21, 2020
    adamgolden likes this.
  9. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    The cross-platform solution is to only use the SceneView camera to get the Physics Scene and using HandleUtility.GUIPointToWorldRay instead of ScreenPointToRay during Edit Mode.

    First, a generic GetCurrentPhysicsScene function. Works for Edit mode, Prefab Edit mode and Play mode:
    Code (CSharp):
    1. public static PhysicsScene GetCurrentPhysicsScene()
    2. {
    3. #if UNITY_EDITOR
    4.   if (Application.isPlaying) return Physics.defaultPhysicsScene;
    5.   if ((SceneView.lastActiveSceneView != null) && (SceneView.lastActiveSceneView.camera != null) && SceneView.lastActiveSceneView.camera.scene.IsValid()) return SceneView.lastActiveSceneView.camera.scene.GetPhysicsScene();
    6. #endif
    7.   return Physics.defaultPhysicsScene;
    8. }
    Then:
    Code (CSharp):
    1. Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
    2. if (GetCurrentPhysicsScene().Raycast(...
     
    TheZombieKiller likes this.
  10. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
    Hi @polemical,

    Could you please submit a bug report for this issue so we can have a look at it? That would be very helpful in order to get this investigated.
     
  11. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    Sure - will do later this morning.
     
  12. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    @LeonhardP I've confirmed this is still a problem in 2020.2.0b1, existing in both normal Edit mode and Prefab Edit mode.

    Case 1275281: Windows Scale breaks SceneView Camera ScreenPointToRay

    Edit: Also exists in 2019.4.9f1
     
    Last edited: Sep 4, 2020
  13. LeonhardP

    LeonhardP

    Unity Technologies

    Joined:
    Jul 4, 2016
    Posts:
    3,136
    Thanks a lot! We'll look into it.
     
    adamgolden likes this.
  14. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Let us know when there's a publicly available entry in the issue tracker. This bug breaks some tools we have. It's already clunky enough to get the mouse position in the scene view, so it's annoying that that's broken now.

    Note that we're seeing this bug in 2020.1, so it's not a 2020.2 issue.
     
  15. Domas_L

    Domas_L

    Unity Technologies

    Joined:
    Nov 27, 2018
    Posts:
    111
    Hello!

    The first issue - Physics.defaultPhysicsScene not working in Prefab Edit Mode - is intentional behavior. The PreviewScene used for Prefab Mode uses a local physics scene in order to not 'pollute' the default physics world. Prefab Mode has its local editing scene separate from the main scenes (Game scenes). In the repro project from the bug report, raycasting against colliders in Prefab Mode is done in the right way: by getting the local physics and making queries against that. Unity will never instantiate the Prefab Mode objects into the Game world as that would break the game logic there.

    The second issue - Event.current.mousePosition giving incorrect coordinates when WindowsScale is not set to 100% - is also expected behavior. In the Game View, the screen coordinates are in pixels, while In the GUI, coordinates are in units independent from the current window scaling. To convert between them, you can multiply/divide by GUIUtility.pixelsPerPoint.

    The third issue - Y coordinate of Event.mousePosition is upside down in scene view - is also not a bug, as the point of origin of Event.mousePosition and Camera.ScreenPointToRay is different. As stated in the documentation, Event.mousePosition uses the top-left corner as the origin, while Camera.ScreenPointToRay uses the bottom-left corner as the origin. More information:
    https://docs.unity3d.com/ScriptReference/Event-mousePosition.html
    https://docs.unity3d.com/ScriptReference/Camera.ScreenPointToRay.html
     
    NoMatchForNoclip and adamgolden like this.
  16. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
  17. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
  18. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    @Baste
    Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
    was mentioned earlier, I'm not sure if you noticed that post but it's what I've switched to and so far has been working as expected.
     
    Baste likes this.
  19. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    Since I came back to this and it might be useful for others;

    HandleUtility.GUIPointToWorldRay(Event.current.mousePosition); works, but only during OnSceneGUI. You often want the mouse position at other points - like when you invoke a [MenuItem] or [Shortcut]. At those times, the position is wrong.

    The solution I've found that has the least hassle is to simply:

    Code (csharp):
    1. [Shortcut("Rain/Play From Mouse Pos", KeyCode.P, ShortcutModifiers.Shift)]
    2. public static void PlayFromMousePos() {
    3.     SceneView.duringSceneGui += PlayFromMousePosImpl;
    4. }
    5.  
    6. private static void PlayFromMousePosImpl(SceneView obj) {
    7.     var mousePos = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition).origin;
    8.     PlayFrom(mousePos);
    9.     SceneView.duringSceneGui -= PlayFromMousePosImpl;
    10. }
    Again, it would be very nice if we had a "works at all times, don't need to think about it" solution for "where is the mouse pos in relation to this scene view's world space?".
     
  20. virtualjay

    virtualjay

    Joined:
    Aug 4, 2020
    Posts:
    68
    This is definitely a broken workflow. I ran into a similar issue with drawing IMGUI elements onto the scene view. It would work fine, until you were at a different windows dpi. Then it'd break. So now I have to multiple every single coord x/y by this.

    Perhaps even more frustrating is that every single IMGUI example in the help should be telling you to do this. Or perhaps only the screen view is not taking DPI into account when you put IMGUI elements on it, but the inspector/editor windows do. Either way, it's terrible.
     
  21. antti_partagames

    antti_partagames

    Joined:
    Nov 5, 2015
    Posts:
    18
    Ran into this issue a while back and it seems Unity's SceneView just doesn't respect the Windows 10 app scaling and you have to do a bunch of manual calculation to fix it. It's a bit annoying as some devs are affected by this and some aren't. There really should be some built-in way to handle this.

    The only proper ways I've gotten the editor working is either set the system app scaling to 100% from Display Settings (Windows 10). For a 4k monitor this feels inadequate as it makes every app's text size etc. miniscule, but it fixes our tools.

    The other solution is to keep using app scaling but find Unity.exe and from Properties -> Compatibility -> override high DPI scaling settings. This is a bit better for me because it keeps other apps scaled while fixing the issues. The con is that Unity starts looking a bit blurry.

    So neither of these are perfect but at least they fix the problem for me without adding some janky Windows-specific code. Really hoping this will be solved in future editor versions.

    EDIT: Looks like I missed the part about EditorGUIUtility.pixelsPerPoint multiplier that helps calculate the SceneView size correctly when using OS scaling. So it's not as bad as I initially thought. I would still like the SceneView to have something like scaledWidth etc. but I can live with this for now :D
     
    Last edited: Feb 8, 2023
    Gekigengar likes this.