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