Get mouse position not aligh towards the edge of screen

Godot Version

4.4

Question

Using camera perspective, I create a plane and shoot a raycast then I place a ball on the collision. However My get_mouse_position object keeps getting miss align towards the end of screen. This becomes more noticeable when moving farther away from origin. How should I fix this?

func look_at_cursor():
	var target_plane_mouse = Plane(Vector3(0,0,1),position.z)
	var ray_length = 1000
	var mouse_position = get_viewport().get_mouse_position()
	var from = cam_pers.project_ray_origin(mouse_position)
	var to = from + cam_pers.project_ray_normal(mouse_position) * ray_length
	var cursor_position_on_plane = target_plane_mouse.intersects_ray(from,to)
	track_ball.global_position = cursor_position_on_plane

Is the ball a billboarded sprite or a 3d object with depth?

a 3d sphere placed at the collision on a plane surface

Is the origin of the sphere model in the center of the sphere? If not, it could account for the discrepancy.

Is your camera mode “perspective”, or “orthographic”? Perspective cameras emulate a real camera lens, so they have lens distortion that’s more obvious the closer you are to the edge of the screen. Orthographic cameras instead map everything in the bounds of the camera 1-to-1with the screen.

If I’m right then this problem isn’t being caused by the ball and mouse being misaligned, it’s being caused by the lens distortion inherent to perspective cameras, since this will effect the ball, but not the mouse cursor.

It appears to be a perspective camera (see OP), but that should be fine; the raycast against a screen-parallel plane should give that mouse position projected onto the plane.

What I think is happening is the 3D ball is being placed slightly in front of the projected position, possibly because its origin is behind the ball rather than centered within it, so the projection math winds up pushing the ball further from the mouse pointer the further it is from the center of the screen.

If the ball was behind the collision plane, I’d expect it to pull towards the center of the screen rather than pulling towards the edge.

Oh, it would only pull to the centre if the camera was positioned far from the plane, if the camera is too close them lens distortion will push to the edge as get_mouse_position() takes the position of the mouse on your viewport and converts it directly to a position in units.

For an extreme example you can set a perspective camera to a full 180 FOV and push it up against a plane; using the above code you’d move the mouse one pixel and the object would zip from one side of the screen to the other. If you move this camera far away, then now everything will pull to the centre.

Switching to orthographic is not guaranteed to fix the issue. If it’s caused by the object being misaligned with the plane like you said then your fix will need to be employed. However, an orthographic camera will make any misalignment more obvious because there will be no image distortion at all.

1 Like

This ball is a child of my Player. It is used as a target for my Player system (aiming, facing back/forth…etc). As Player moves away from original position the distortion or “misalignment” becomes too obvious.

For example, my game is a 3d sidescroller.

This is at the start of level (player at global.x = 0)

This is as I move farther to right towards the end of the level (player at global.x = 200+)

You can see I have to move cursor quite a distance before my player looking back.

Is there anyway to fix this for perspective camera? For your info:

The Camera is not attached to player, it is moved with player by codes.
The Camera position.z is 10
The FOV is 59
The target_plane’s position.z is at player’s position.z (which is zero).

How does a 3d top down shooter like this avoid this problem? I’m using pretty much the same targeting code.

What’s your field of view set to?

my fov is 59

In principle you have all the parts to do this by hand; it might be worth doing that to see what you get:

  • camera.z = 10.0
  • plane.z = 0.0
  • vertical FOV = 59.0
  • let’s assume your window height is 1080 pixels

You have a right angle triangle. It’s 29.5 degrees (half of 59) at the camera, 90 degrees directly ahead of the camera on the plane, and 60.5 degrees at the top of the screen. The adjacent side (to the camera) is length 10.0. You can use trigonometry to get the length of the opposite side, and use that to scale the mouse position to get the ball position.

You can do the same horizontally by multiplying by the aspect ratio.

Ok, First of all thank you for trying to help me.

However, since I am not good with math, would there be any other possible way to tackle this issue?

I am also wondering wouldn’t other games that use raycast from mouse have the same distortion problem?

Probably the best answer is to print things and see what you’re getting.

At the basic level, it looks like the ray-plane intersection ought to be fine; you’ve got an origin and a normal, for the ray, you’re scaling the normal by ray length and adding it to the origin, you’re checking if it intersects the plane. That should give you the exact point on the screen where the mouse points, at the depth of your plane, at least in this case.

I’d consider printing all those numbers to the logger or maybe to debug labels onscreen and seeing if they look sane at runtime. I’d also print the global_position of your sphere.

@boe_taito 's point is good as well; if you switch to an orthographic camera and the problem goes away, it means your sphere is at the wrong depth.

1 Like

Testing this with a clean project. This is with orthogonal camera. You can still see distortion at left and right.

The orthogonal camera size is 20
Camera position at (0,0,29.4)

Trackball meshInstance origin at center
Plane at position.z 0
My raycast length is 1000

Code’s at world.gd
SceneTree:
-world
–Camera
–track_ball

func _physics_process(delta: float) -> void:
	
	var target_plane_mouse = Plane(Vector3(0,0,1),0)
	var ray_length = 1000
	var mouse_position = get_viewport().get_mouse_position()
	var from = cam_pers.project_ray_origin(mouse_position)
	var to = from + cam_pers.project_ray_normal(mouse_position) * ray_length
	var cursor_position_on_plane = target_plane_mouse.intersects_ray(from,to)
	track_ball.global_position = cursor_position_on_plane
	
	$Label.text = ("mouse_position: " + str(mouse_position))
	$Label2.text = ("track_ball: " + str(track_ball.global_position))
	$Label3.text = ("Cam_type: " + str($Cam_pers_level.projection))
	$Label4.text = ("from: " + str(from))
	$Label5.text = ("to: " + str(to))

Any idea what’s goin on?

It seems the raycast length is the cause. The more I increase its length ,to say 1000000, the less distortion becomes, and vice versa. I don’t know why.

Feel free to test it. Open a new project, add a world and a child sphere meshInstance and camera, then copy my code to the world.gd

Even though this “fixes” my problem it still doesn’t make sense to me. I will leave this unsolved until better solution comes along.

That is weird, and shouldn’t be happening with an ortho camera.

I agree, it shouldn’t happen. But it does, I just tested. Camera distance seems to also have an effect. With 100.0 the ball is way off! I have no idea why… A bug in Godot?

This works however:

func _physics_process(delta: float) -> void:
	var mouse_position = get_viewport().get_mouse_position()
	var from = cam_pers.project_ray_origin(mouse_position)
	track_ball.global_position.x = from.x
	track_ball.global_position.y = from.y

Is this ok for your game?

Edit: If your camera always faces the same direction, you can use my code and don’t need a ray at all. You can even take out the ray_length and just set the z-position manually.

But if you want it to work even if the camera rotates, use @Gustjc’s code below.

1 Like

You are misunderstanding how intersects_ray works. It uses a direction, and you are passing a position. If you want to use a segment like your example you need to use intersects_segment.

The reason it ‘works’ with a very high ray_length is that when the position is very var away, it is approximately equal to the direction when normalized.

A proper way to use the intersects_ray using your example would be:

func _physics_process(delta: float) -> void:
	var mouse_position = get_viewport().get_mouse_position()
	var target_plane_mouse = Plane(-cam_pers.global_basis.z,5)
	var from = cam_pers.project_ray_origin(mouse_position)
	var dir = cam_pers.project_ray_normal(mouse_position)
	var cursor_position_on_plane = target_plane_mouse.intersects_ray(from,dir)
	track_ball.global_position = cursor_position_on_plane

Which works as expected

2 Likes

Thank you@Gustjc for solution and explanation, It works now. Thanks to all others as well for the help.

@tassup I couldn’t get your code to work, but it’s ok now.

Fixed it. Didn’t want to leave a stupid mistake there, even though this was solved :smiley:

1 Like