Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

world_to_map() returns inconsistent values when using raycast #46561

Open
Tracked by #45333
edddieee opened this issue Mar 1, 2021 · 3 comments
Open
Tracked by #45333

world_to_map() returns inconsistent values when using raycast #46561

edddieee opened this issue Mar 1, 2021 · 3 comments

Comments

@edddieee
Copy link

edddieee commented Mar 1, 2021

Godot version:

3.2.3-stable_x11.64 (Also tested in 3.1.2-stable_x11.64)

OS/device including version:

Manjaro 20.2.1 Nibia

Issue description:

The world_to_map() sometimes return inconsistent values when using the intersect_ray() method. See the output from the image below:

1
2

Looking the z axis from output you will see an inconsistent values between the first and second image.
I also tried to reproduce the world_to_map method, dividing the result.position by the cell_size and then using the floor() method, but in both cases I get the same inconsistent value.

	var space_state = get_world().direct_space_state
	var start = camera.project_ray_origin(crosshair.position)
	var end = start + camera.project_ray_normal(crosshair.position) * ray_length
        var cell_size = 2
	
	var result = space_state.intersect_ray(start, end, [self.get_rid()])
	if result and Input.is_action_just_pressed("ui_accept"):
		print(str(result.position) + " # WORLD SPACE")
		print(str(result.position / cell_size) + " # CELL COORDS")
		print(str((result.position / cell_size).floor()) + " # CELL COORDS with floor()s")
		print(str(grid_map.world_to_map(result.position)) + " # world_to_map()")
		print("====================")

Steps to reproduce:

  • Create a GridMap with a basic box as a tile.
  • Use the raycast to hit any face of the box.
  • Get the result from the ray.
  • Pass the result.position to the world_to_map().
  • Print the value.
  • Repeat the process in another point in the box until you see an inconsistent value.

Minimal reproduction project:

GridMapRaycastBug.zip

@pouleyKetchoupp
Copy link
Contributor

The raycast hit position is not accurate enough to retrieve the corresponding cell by position, because the result is exactly at the edge between two cells, and mathematical approximations will give you inconsistent results.

There's currently no easy way to get the proper cell that was hit by the raycast though, so that needs to be fixed in the GridMap API. Since it's already possible to get the hit shape from the raycast result (it's the internal shape index from the physics server, so it's not very useful at the moment), an easy way would be to add a function to GridMap like shape_index_to_map() in order to get the map coordinates from a shape. It can be useful for collision information too.

As a workaround, in your case you can add a little bit of extra distance to your raycast hit position to make sure it's inside the box when a hit occurs, and it will give you consistent results:

var space_state = get_world().direct_space_state
	var start = camera.project_ray_origin(crosshair.position)
	var ray_dir = camera.project_ray_normal(crosshair.position)
	var end = start + ray_dir * ray_length
	
	var result = space_state.intersect_ray(start, end, [self.get_rid()])
	if result and Input.is_action_just_pressed("ui_accept"):
		var hit_pos = result.position
		hit_pos += ray_dir * 0.001 # add some extra distance to ensure we're inside the hit cell
		print(str(hit_pos) + " # WORLD SPACE")
		print(str(hit_pos / 2) + " # CELL COORDS")
		print(str((hit_pos / 2).floor()) + " # CELL COORDS with floor()s")
		print(str(grid_map.world_to_map(hit_pos)) + " # world_to_map()")
		print("====================")

@edddieee
Copy link
Author

edddieee commented Mar 1, 2021

Hey @pouleyKetchoupp , thanks for all information! The workaround works very well!

@michasng
Copy link

michasng commented Oct 28, 2023

With Godot 4.1.2 you can actually get the normal vector of the hit position to calculate which cell was hit and which cell was in front of that cell. Like this:

func _input(event: InputEvent):
	var crosshair_position = camera.get_window().size / 2 # cursor at the center, because I'm using MOUSE_MODE_CAPTURED
	var ray_start = camera.project_ray_origin(crosshair_position)
	var ray_dir = camera.project_ray_normal(crosshair_position)
	var ray_end = ray_start + ray_dir * RAY_LENGTH

	var intersect_ray_params = PhysicsRayQueryParameters3D.create(ray_start, ray_end, 0xFFFFFFFF, [get_rid()])
	var result = get_world_3d().direct_space_state.intersect_ray(intersect_ray_params)

	if not result or not is_instance_of(result.collider, GridMap):
		return

	var grid_map = (result.collider as GridMap)
	var normal_offset = grid_map.cell_size / 2 * result.normal
	# the cell that has been hit
	var cell_world_position: Vector3 = result.position - normal_offset
	var cell_position = grid_map.local_to_map(cell_world_position)
	# the "empty" cell in front of the cell being hit
	var empty_world_position: Vector3 = result.position + normal_offset
	var empty_position = grid_map.local_to_map(empty_world_position)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants