godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.17k stars 98 forks source link

Add single accessible high level method to cast ray from mouse position into 3D scene and return collision point and collision object #6784

Open golddotasksquestions opened 1 year ago

golddotasksquestions commented 1 year ago

Describe the project you are working on

This applies to every 3D project I ever worked on and ever will work on.

Describe the problem or limitation you are having in your project

This is one of the most common things I want to do in any 3D project:

Cast a Ray from the 2D mouse-position in the viewport forward along the Camera normal and see it it collides with anything. If so, return the collision point as well as the collision object.

In order to do this right now in Godot3 or Godot4 I need to:

  1. Get a raycast (either as a Raycast node or from the direct-space-state),
  2. get the camera,
  3. get the mouse position offset in the viewport,
  4. calculate a "from" point ,
  5. calculate a "to" point (project normal from camera),
  6. perform the cast (and possibly force update),
  7. check for a collision object,
  8. get the collision point.

Code examples: https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html#d-ray-casting-from-screen https://godotengine.org/qa/134656/godot-cast-from-mouse_position-towards-camera-orientation https://godotforums.org/d/33479-godot-4-raycasting-to-get-mouse-position-in-3d

Since it is needed pretty much everywhere in any 3D project its super tedious to reimplement it again and again. It's also something you can't intuitively deduct or use autocomplete, you have go online and look for tutorials or QA questions or learn about the process plowing the documentation.

Would also solve https://github.com/godotengine/godot-proposals/issues/979

Describe the feature / enhancement and how it helps to overcome the problem or limitation

Add a high level method which is accessible from any 3D node or Viewport:

Array cast_ray_from_mouse(float distance = 10000.0)

or simple when used:

var mouse_collision_point = cast_ray_from_mouse()[1]

The Array this method returns has at least two elements (collision_object, collision_point), but I would prefer to have more: [collision_object, collision_point, collision_normal, ....]

The float distance argument is optional (default value is 10000.0) and is the ray length in ray_start + cam.project_ray_normal(mouse_pos_viewport) * ray_length

So when I call the single cast_ray_from_mouse() method from any 3D node or viewport, Godot gets the currently active camera for me, does the camera normal projection from the mouse position viewport offset and casts a ray to the to point and checks if there is any collision object in the rays path. If there is, returns the Array with the first collision object and first collision point and normal ...

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

See above links and description

If this enhancement will not be used often, can it be worked around with a few lines of script?

If would be used VERY often throughout any 3D genre. There already is a workaround, though the experience is not user friendly or intuitive.

Is there a reason why this should be core and not an add-on in the asset library?

This is about drastically improving the 3D usability out of the box for one of the most common tasks.

TheDuriel commented 1 year ago

Variation used to detect if the player clicked on a floor plane. An RTS or ARPG would use this.

onready var _viewport: Viewport = get_viewport()
onready var _camera: Camera = _viewport.get_camera()

const FLOOR_HEIGHT: float = 0.0
const INTERSECTION_PLANE: Plane = Plane(Vector3(0, 1, 0,), FLOOR_HEIGHT)

func cast_camera_ray():
    var mouse_pos: Vector2 = _viewport.get_mouse_position()
    var mouse_normal: Vector3 = _camera.project_ray_normal(mouse_pos)

    var intersection: Vector3 = INTERSECTION_PLANE.intersects_ray(_camera.global_transform.origin, mouse_normal)

Variation used when you need an actual collision check.

onready var _viewport: Viewport = get_viewport()
onready var _camera: Camera = _viewport.get_camera()

func cast_camera_ray():
    var mouse_pos: Vector2 = _viewport.get_mouse_position()
    var mouse_normal: Vector3 = _camera.project_ray_normal(mouse_pos)

    var space_state: SpaceState = get_world().get_space_state()

    var result: Dictionary = space_state.cast_ray(mouse_pos, mouse_normal * ray_length)

Pseudo Code this is nabbed from a really old project of mine which used this.

If we compare these tested examples, they are a lot simpler than OP claims the process is. There is no math required, just 2 function calls.

Why is it simpler? Because we are not hacking the RayCast Node to do something it isn't designed. And because we are relying on the built in functions to do the math for us.

can it be worked around with a few lines of script?

Yes.

But we need further education on this. Casting Rays through code is easy, powerful, and something developers need to know about. But the existence of the RayCast nodes creates a scenario in which newbies like OP here are not aware of its utility.

https://docs.godotengine.org/en/stable/tutorials/physics/ray-casting.html

This page should be more prominently featured. And should include an example for through-camera casting.

golddotasksquestions commented 1 year ago

I have already linked and mentioned the documentation and various code samples which covers these methods in this proposal Duriel. I have also mentioned direct space state explicitly.

The fact that there are a number of different lengthy solusions, of which none are intuitive or able to find via single autocomplete does not make this less confusing for people who just want a single straight forward method to click on stuff in 3D.

TheDuriel commented 1 year ago

Which is why I am in favor of education.

Adding a method does not solve the fact people don't know to use it.

golddotasksquestions commented 1 year ago

To iterate: The issue here is not a lack of documentation or tutorials. The currently available processes are well documented in the documentation itself as well as in QA and community channels and countless tutorials. The problem in my opinion is a lack of convenience and user-firendlyness for one of the most common things anyone will want to do in any 3D project.

TheDuriel commented 1 year ago

Since you posted the wrong way to go about it in your proposal. I believe it is an education issue with the resources you linked. I'll leave it at that.

badunius commented 1 year ago

@TheDuriel

Variation used when you need an actual collision check.

how come you skipped the entire

ray_query.from = from
ray_query.to = to
ray_query.collide_with_areas = true
raycast_result = space.intersect_ray(ray_query)

part? And what is educational about it besides the engine-specific procedures?

I'm genuinely interested, because so far I used input_event of a StaticBody3D to get event position, which doesn't require neither ray casting nor limited by a constant elevation

Watunder commented 3 weeks ago

https://github.com/godotengine/godot/pull/97257 RuntimeNodeSelect::_find_3d_items_at_pos