I'm not sure I understand your limitations, so I will give you a few approaches, and hopefully this gets you to the solution along the way.
This is not an exhaustive list.
Pickable Object
If you have a CollisionObject
(e.g. the board), you can set input_ray_pickable
to true
on it.
Then connect the input_event
signal or override the _input_event
method, where you will get - among other parameters - the position in 3D of the pointer when it is over the 3D object.
RayCast Node
If you can't - or rather not - depend on pickable objects, but still have something to collide with, you can use a RayCast
node. To make it follow the pointer, you can do this:
var ray_length = 100 # some large number
var mouse_pos = get_viewport().get_mouse_position()
$RayCast.transform.origin = camera.project_ray_origin(mouse_pos)
$RayCast.cast_to = camera.project_ray_normal(mouse_pos) * ray_length
$RayCast.force_update_transform()
$RayCast.force_raycast_update()
Pay attention to project_ray_origin
and project_ray_normal
. Should give you the direction for the ray that goes toward the mouse_pos
, given the camera projection and Viewport
size. On the other hand, project_ray_origin
should give you the origin for the ray, which is the position of the Camera when the camera projection is perspective, but the method also supports orthogonal projection.
Calling force_update_transform
and force_raycast_update
is only necessary if you are moving the RayCast
multiple times in the same physics frame.
You would then check $RayCast.is_colliding()
(which is a way to detect if the pointer is out of the board, for example), and read get_collision_point
to get the position. You can also read get_collider
which will give you the object the RayCast
collided with (which you can check if it is the board).
Note: make sure the RayCast
is enabled, it is disabled by default.
Ray-plane intersection
If you want to intersect with an axis aligned plane instead of some object, we can handle it with some vector algebra.
We need to define our ray, and our plane.
For the ray part, the code is basically what we did for the RayCast
node above:
var mouse_pos = get_viewport().get_mouse_position()
var origin = camera.project_ray_origin(mouse_pos)
var direction = camera.project_ray_normal(mouse_pos)
Conceptually, we define a parametric form of the ray like this:
origin + direction * distance
Where distance
is the parameter. We can assume that direction
is a unit vector (a vector of length one) so that distance
gives you the length from origin
.
The plane will be defined by an coordinate set to a constant, in this case y = 0
.
We want to find a point along the ray, that satisfy that (y = 0
). We can have a parametric form of the ray like this:
(origin + direction * distance).y = 0
That is:
origin.y + direction.y * distance = 0
Solve for distance
:
distance = -origin.y/direction.y
Note that this code would be a division by zero when direction.y = 0
. That is, if the ray is parallel to the plane (in which case there is no intersection), so you want to check for that.
With that, we can write the rest of the code:
if direction.y == 0:
return
var distance = -origin.y/direction.y
var position = origin + direction * distance
And that position
should be intersection with the y = 0
plane.
Addendum: The intersection could be behind the camera (when the camera is looking away from the plane, for example the plane is below the camera and the camera is looking upwards), in which case the distance will be negative, you may want to check for that too.