Easiest way of detecting a click on a 2D object in 3D Space

Godot Version

4.2.1-stable_mono

Question

Hello everybody,

I have a grid map (think hex-map, but slightly different) that is 2D functionally but lives in 3D space. When I click a mouse button I want to know where on that 2D grid I clicked.

So far I have done this manually using a Plane() .

	private Vector3? ScreenToGridPlane()
	{
		Vector2 mousePos = GetViewport().GetMousePosition();
		Camera3D camera = GetTree().Root.GetCamera3D();
		Vector3 rayOrigin = camera.ProjectRayOrigin(mousePos);
		Vector3 rayDirection = camera.ProjectRayNormal(mousePos);

		Plane plane = new (_gridPlaneNormal, _centerPos);

		return plane.IntersectsRay(rayOrigin, rayDirection);
    }

This however fails if the Grid Transform is changed in any way (other then rotating, since I manuallyadjusted _gridPlaneNormal). My method returns the worldPosition and not localPosition.

Thus I thought it might be a lot easier to just an Area3D with a CollisionShape3D.

Sadly there is not really a Plane Shape. Only thing I can think of is using a very flat Box Shape.

Am I missing any other method if I don’t want to manually calculate the local Position from what Plane.IntersectsRay() returns.

Thanks already!

Assuming you’re running this in _physics_process() you could use get_collision_point() to get the global coordinates point where the ray hits.
Then you do some trigonometry.

extends Node3D

func _physics_process(delta):
	var ray: RayCast3D = $RayCast3D
	var c: PhysicsBody3D =  ray.get_collider()
	if c:
		var p = ray.get_collision_point() # World space point
		var lp = p * c.transform # Local space point
		var sp = lp / c.scale # Scaled back
		var uv = Vector2(sp.x, sp.y) # Discard depth
		var uvc = Vector2.ONE - uv * 0.5 # Make top left (0, 0) and bottom right (1, 1)
		# the rest of your local calculations...
		var result
		return result

This is an example of a Node3D with a RayCast3D child getting a point in UV-space on a cube, but it could be any mesh. You local calculations would be converting to map coords from the UV [0-1] and then to tile and whatever else you need in that.
The important part is the face is aligned to the z-axis, so we discard the z coord. For non-aligned faces it is more complicated.

1 Like

Thanks for your answer.

I just realised that I am somewhat of a dunce:

  1. An Area3D with a flat CollisionShape3D also only returns the global Position (when using the InputEvent Signal
  2. There two methods called ToLocal and ToGlobal, which should solve my problem when I use it with Area3D.

Sorry one more question.

I just used a CollisionObject3D with a BoxShape object, where I’ve set Y = 0. The InputEvent signal from the Area3D seems to work just fine.

I was kind of thinking setting Y = 0 might cause a problem, but I guess it might only be problematic for collisions betweens two colliders as its paper-thin and would often be ignored.

The only time a 0-thin obstacle is a problem is if an object (physicsbody) is moving faster than it is long along its movement. Then it can skip through.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.