How to convert mouse coordinates to 3d position?

inspired_coderinspired_coder Posts: 6Member
edited October 2017 in 3D

I want to move 3d cube with mouse cursor, so i need convert mouse position for it. Can anyone help me?

Thanks in advance.

Answers

  • TwistedTwiglegTwistedTwigleg Posts: 1,000Admin

    You’ll probably need to use raycasting to convert the mouse position to 3D space using a ray. Here’s a link to the documentation on raycasting which has some sample code that should help.

  • inspired_coderinspired_coder Posts: 6Member

    Yes, thank you, but there is not clear. I am using that function to get 3d world coordinates:

    camera.project_ray_normal(get_viewport().get_mouse_pos())

    ...but these coordinates cannot corresponds mouse position. Where is mistake?

  • TwistedTwiglegTwistedTwigleg Posts: 1,000Admin
    Answer ✓

    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

  • 1000h1000h Posts: 133Member
    edited December 2017

    I'm trying to do the same thing right now.
    At first I tried using

    camera.project_position(click_event_position)

    If you use these values to set the velocity of the object you want to move, they are relative to the origin. This means that the velocity of the physics body changes depending on where you click on the screen, and not where you click according to the physics body.

    Intuitively, you'd want the velocity to change depending on where you click relative to the physics body (e.g. click below the actor he goes down), but this is not the case when projecting the click event position.

    Then I thought I might transform (using Transform.xform(vector)) the projected position by the actors origin. This does not work either. The projected position is now no longer relative to the origin on which the camera is centered, but now relative to a moving body, and so the velocity will increase even more dramatically.

    When I was playing around, instead of using KinematicBody.move() I tried KinematicBody.move_to(), which is a teleportation function rather than a velocity type function. This got the object to move immediately (teleporting) to the position if I multiplied the projected click position by a large enough floating value, which to me seemed a bit arbitrary, and the value I chose that kept the object within the screen bounds was about 300. The reason this happens, remember, is the projected click always comes back as some value between 0 and 1, so you need to multiply it by some scalar.

    Anyway this is as far as I've gotten now, so I too haven't figured it out yet. I have looked a bit into some math but I am not mathy, so I don't know how to apply any of it yet. Of course the idea is to get it to work without using if else statements.

    The hacky way is setting up a navigation mesh plane veritcally so that it fills the screen. Your object would then travel to a point on the navigation mesh plane, not according to a projected screen point. This way is much faster assuming you are as bad at math as I am, but I am still trying to figure out the mathy way because reasons

  • 1000h1000h Posts: 133Member
    edited December 2017

    So the way I am settling on for now (although I'll probably switch to my navigation mesh idea) isn't as mathy as I'd have hoped, but it does the trick (generally).

    Note: This way works only if the camera is set such that the y and z coordinate of its transform origin are 0.

    So I have a Control that is the detector, and it does this:

            var event_point = Vector2()
            event_point = event.pos
    
            var dir = Vector3()
            var projected_click = Vector3()
            projected_click = cam.project_position(event_point)
    
            dir = projected_click
            dir*= cam.get_zfar()
            dir.x = 0
    
            kine.set('dir',dir)
            kine.set_fixed_process(true)
    

    Then on the kinematic simply do this:

        var new_position = get_global_transform().origin.linear_interpolate(dir,delta)
        move_to(new_position)
    

    I noted something interesting while doing this.
    I am using Godot 2.1.xxx something
    Usually when I set up a camera, I want to set the z_far to max because why not. But instead of typing in 4096, I type in like, 10000000 just so it pops up to the max and it is faster it seems to type that way. However, I realized that if you type in 1000000 or something much larger, then try to retrieve the zfar through cam.get_zfar(), although it is displaying at 4096 in the editor, get_zfar() will pick up the float of whatever crazy number you type in to cap it off in the editor. So if you did what I did, you need go back and type in 4096 in the editor, or when you use get_zfar in the code I've provided, it will shoot your dude off in some crazy direction

  • 1000h1000h Posts: 133Member
    edited December 2017

    I am now realizing that when the camera projects a position using project_position, the position is based on the camera's transform origin and the znear value. If you project a position and you have a near of .1, it will project the position within some range of the camera's position and .1. If you are at a position of 32 with a near of .1, the projected position will be something like 31.9. Likewise, if you have a near of 10, and a position of 32, it will be somewhere in the 20s. By altering this near value, I've found that the code works above even with strange camera orientations, but then you run into clipping issues.

    I realize that a way to get around this issue of having the correct position but avoiding clipping is to set the z_near to some greater value, store the projected click position in this state, and then immediately switch the z_near to some near 0 value again that avoids clipping!

  • SaranSaran Posts: 14Member

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Hi, could you help me?
    I have code as below, but the result is always empty, I don't know why. I tried making a plane mesh but it didn't help:

    var ray_length = 100000
    var from = $Camera.project_ray_origin(event.position)
    var to = from + $Camera.project_ray_normal(event.position) * ray_length
    var space_state = get_world().get_direct_space_state()
    var result = space_state.intersect_ray( from, to )
    if result.size() != 0:
        print(result.collider)

    event.position is from button pressed event.
  • TwistedTwiglegTwistedTwigleg Posts: 1,000Admin

    @Saran said:

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Hi, could you help me?
    I have code as below, but the result is always empty, I don't know why. I tried making a plane mesh but it didn't help:

    var ray_length = 100000
    var from = $Camera.project_ray_origin(event.position)
    var to = from + $Camera.project_ray_normal(event.position) * ray_length
    var space_state = get_world().get_direct_space_state()
    var result = space_state.intersect_ray( from, to )
    if result.size() != 0:
      print(result.collider)

    event.position is from button pressed event.

    I can certainly try. :smile:

    First, have you looked at the ray-casting section of the documentation? That is where I got the code from in my example. One problem you may be having is getting the space state, as it may be locked when you are trying to use it.

    Another thing you will need to do is add a collision shape to the plane mesh you want to collide with, as otherwise the raycast will not hit anything and result will be null.

    So, if you have a collision shape attached to the plane mesh, then the following code should work (untested):

    const RAY_LENGTH = 1000;
    var send_ray = false;
    var event_position = Vector2(0, 0);
    var camera = null;
    
    func _ready():
        camera = get_node("Camera");
    
    func _physics_process(delta):
        if (send_ray == true):
            send_ray = false;
            var from = camera.project_ray_origin(event_position);
            var to = from + camera.project_ray_normal(event_position) * RAY_LENGTH;
            var space_state = get_world().get_direct_space_state();
            var result = space_state.intersect_ray(from, to);
            if (result != null):
                print ("collided with: ", result["collider"].name, " at position: ", result["position"])
    
    func _input(event):
        if (event is InputEventMouseButton):
            if (event.pressed == true and event.button_index == 1):
                send_ray = true;
                event_position = event.position;
    

    Looking at my old answer, I may have given a overly complicated solution.
    In theory (and I have not tested, so I do not know for sure) if you are only wanting to convert from mouse position to world position, you could just use code like this (untested):

    func _input(event):
        if (event is InputEventMouseButton):
            if (event.pressed == true and event.button_index == 1):
                var camera = get_node("Camera")
                var from = camera.project_ray_origin(event.position);
                var to = from + camera_project_ray_normal(event.position) * distance_from_camera;
                # Apply the position to whatever object you want (we'll assume a node named "Cube")
                get_node("Cube").global_transform.origin = to;
    

    Anyway, hopefully this helps!

  • ThortillaThortilla Posts: 3Member

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Jajajajaja I dont know if some of you still be here, I uses this code above and it works really great, thank you very much!, but now I need to proyect the raycast that only impact in one determinated object (in my case the flour), I dont know if you can understand me well because english is not my first language. In other words, I need to make that this part of the code:
    var result = space_state.intersect_ray( from, to )
    give me where the ray is coliding with the flour (without taking in account that there are more objects at the front.

    Thanks!

  • TwistedTwiglegTwistedTwigleg Posts: 1,000Admin

    @Thortilla said:

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Jajajajaja I dont know if some of you still be here, I uses this code above and it works really great, thank you very much!, but now I need to proyect the raycast that only impact in one determinated object (in my case the flour), I dont know if you can understand me well because english is not my first language. In other words, I need to make that this part of the code:
    var result = space_state.intersect_ray( from, to )
    give me where the ray is coliding with the flour (without taking in account that there are more objects at the front.

    Thanks!

    I think most of us are still here, doing stuff here and there :smile:

    I’m glad the code worked. It is from the documentation on raycasting, which is where I learned about it initially.

    To answer your question, I would suggest putting all of the objects you want your raycast to collide with, like your floor, either on a separate collision layer, or have the objects on multiple collision layers.

    Both in DirectSpace and DirectSpace2D, the intersect_ray function can take a list of collision layers as an argument. Using this, you can make it where the raycast only collides with objects on specific layers. Using this, you can make it where you can control what a raycast can collide with by changing the collision layer(s) they are on.

    You’ll just need to set the objects to whatever collision layer(s) you want, figure out what the Bitwise value is for the layer(s) you need to collide against, and then pass that to the intersect_ray function.

    In this QA post, KidsCanCode explains how to convert from a collision layer to a integer value.

    Once you have the integer value for the collision layer, you just need to pass it in to intersect_ray. Something like this (untested):

    var result = space_state.intersect_ray(from, to, self, [collision_mask])
    

    And if you want to check against multiple layers, then you just need to add more collision masks to the list:

    var result = space_state.intersect_ray(from, to, self, [collision_mask, collision_mask_two, collision_mask_three])
    

    Hopefully this helps! :smile:

  • ThortillaThortilla Posts: 3Member

    @TwistedTwigleg said:

    @Thortilla said:

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Jajajajaja I dont know if some of you still be here, I uses this code above and it works really great, thank you very much!, but now I need to proyect the raycast that only impact in one determinated object (in my case the flour), I dont know if you can understand me well because english is not my first language. In other words, I need to make that this part of the code:
    var result = space_state.intersect_ray( from, to )
    give me where the ray is coliding with the flour (without taking in account that there are more objects at the front.

    Thanks!

    I think most of us are still here, doing stuff here and there :smile:

    I’m glad the code worked. It is from the documentation on raycasting, which is where I learned about it initially.

    To answer your question, I would suggest putting all of the objects you want your raycast to collide with, like your floor, either on a separate collision layer, or have the objects on multiple collision layers.

    Both in DirectSpace and DirectSpace2D, the intersect_ray function can take a list of collision layers as an argument. Using this, you can make it where the raycast only collides with objects on specific layers. Using this, you can make it where you can control what a raycast can collide with by changing the collision layer(s) they are on.

    You’ll just need to set the objects to whatever collision layer(s) you want, figure out what the Bitwise value is for the layer(s) you need to collide against, and then pass that to the intersect_ray function.

    In this QA post, KidsCanCode explains how to convert from a collision layer to a integer value.

    Once you have the integer value for the collision layer, you just need to pass it in to intersect_ray. Something like this (untested):

    var result = space_state.intersect_ray(from, to, self, [collision_mask])

    And if you want to check against multiple layers, then you just need to add more collision masks to the list:

    var result = space_state.intersect_ray(from, to, self, [collision_mask, collision_mask_two, collision_mask_three])

    Hopefully this helps! :smile:

    Wow, thank you so much! I will try it! :smiley:

  • ThortillaThortilla Posts: 3Member

    @TwistedTwigleg said:

    @Thortilla said:

    @TwistedTwigleg said:
    This is the code that projects a Ray from the mouse coordinates

    var ray_lenght = 1000
    var mouse_pos = get_viewport().get_mouse_pos()
    var camera = get_node("camera")
    var from = camera.project_ray_origin(mouse_pos)
    var to = from + camera.project_ray_normal(mouse_pos) * ray_length
    

    Then you use those coordinates to raycast using intersect_ray in the direct space state.
    Something like this:

    var space_state = get_world().get_direct_space_state()
    # use global coordinates, not local to node
    var result = space_state.intersect_ray( from, to )
    

    Then you can do whatever you need with the results of the raycast.

    Jajajajaja I dont know if some of you still be here, I uses this code above and it works really great, thank you very much!, but now I need to proyect the raycast that only impact in one determinated object (in my case the flour), I dont know if you can understand me well because english is not my first language. In other words, I need to make that this part of the code:
    var result = space_state.intersect_ray( from, to )
    give me where the ray is coliding with the flour (without taking in account that there are more objects at the front.

    Thanks!

    I think most of us are still here, doing stuff here and there :smile:

    I’m glad the code worked. It is from the documentation on raycasting, which is where I learned about it initially.

    To answer your question, I would suggest putting all of the objects you want your raycast to collide with, like your floor, either on a separate collision layer, or have the objects on multiple collision layers.

    Both in DirectSpace and DirectSpace2D, the intersect_ray function can take a list of collision layers as an argument. Using this, you can make it where the raycast only collides with objects on specific layers. Using this, you can make it where you can control what a raycast can collide with by changing the collision layer(s) they are on.

    You’ll just need to set the objects to whatever collision layer(s) you want, figure out what the Bitwise value is for the layer(s) you need to collide against, and then pass that to the intersect_ray function.

    In this QA post, KidsCanCode explains how to convert from a collision layer to a integer value.

    Once you have the integer value for the collision layer, you just need to pass it in to intersect_ray. Something like this (untested):

    var result = space_state.intersect_ray(from, to, self, [collision_mask])

    And if you want to check against multiple layers, then you just need to add more collision masks to the list:

    var result = space_state.intersect_ray(from, to, self, [collision_mask, collision_mask_two, collision_mask_three])

    Hopefully this helps! :smile:

    Hi! it is me again jajaja, I finally use this and it works perfect thank you again! The thing is that I have a problem with the proyect that I am developing, I use this RayCast to know the point that in the 3D world, my mouse is pointing to, to make my 3D character rotate, in other words the character look to this point (only to the sides not up and down).
    The problem is that I have a camera that follows the character through the map (and from this camera I proyect the RayCast), this camera doesn't rotate, only follows the character, and I dont know why, when my character moves, the position of where my mouse is in the 3D world changes also without moving it through the screen. I'm not very experienced and I making some projects but I want learn why this is happening and how to fix it.
    The thing is that I dont know if it is a problem with the raycast (is proyecting in a specific area or somthing like this) or a problem with the rotating system, so please someone that know a lot of this can help me? thank you all!

    PD: my rotation system works using this:

    var character = get_node(".")
    
    var char_rot = character.get_rotation()
    
    char_rot.y = get_node("/root/Global").angle
    character.set_rotation(char_rot)
    

    Here are two images that I hope that can help you to understand better the problem:

    I dont know why but I cannot capture the image with the mouse jajaja but in both images the mouse at the same position

  • TwistedTwiglegTwistedTwigleg Posts: 1,000Admin

    Without looking at the project, my hunch is that the camera node is a child of the player, and so when the player moves, so does the camera. While I’m not positive, the rotation code does not look like it should be causing any issues. Granted, I’m just guessing based on the images and having made this mistake several times.

Sign In or Register to comment.