Godot: Tilemap world_to_map() does not get the correct tile in combination with Raycast2D

Created on 20 Jan 2020  路  6Comments  路  Source: godotengine/godot

Godot version: 3.1 stable, 3.2 rc1

OS/device including version: Win7

Issue description:
When casting a ray onto a tilemap to detect a tile, I would expect for world_to_map() to return the Tile directly behind the ray collision point, in the direction of the ray, not on the right and below the collision point.
But since get_collision_point() returns a point exactly between the two tiles and world_to_map() returns the tile right and below the given point, the wrong tile is returned from world_to_map():

tileset_mining

Steps to reproduce:

extends RayCast2D

func _physics_process(delta):
    if Input.is_action_just_pressed("Mine_Left") and is_colliding():
        var target = get_collider()
        if target.is_class("TileMap"):
            var point = get_collision_point()
            var map_point = target.world_to_map(point)
            target.set_cellv(map_point, 0, false, true)

Minimal reproduction project:
Tileset_Mining.zip

discussion

All 6 comments

The behavior is kind of expected then. Instead of using the collision point, which is at the border of the tile, you should probably use the tip of your raycast instead.

I guess you could also do:

var safe_margin = 1.0 # increase this if doesn't work
var point = get_collision_point() - get_collision_normal() * safe_margin

That way you can kind of prevent these edge-cases where floating point math is not your friend.

Might be also related to something similar issue as in #26615.

@Xrayez Thank you so much, this works great!
If that's the expected way to solve this problem, it probably should be documented somewhere.

@golddotasksquestions maybe, yet this specific problem doesn't seem to pertain to tilemap or raycast in isolation so not sure if this can be properly documented, at least in the class reference. I find it difficult to come up with proper wording to describe this, so it does feel like a workaround currently.

If you re-run the raycast pointing from the origin to the collision point again exactly, is_colliding should return true. I know that because I do quite similar technique for a use case where I want to prevent continuous raycast detection, the only difference is that - becomes +:

var point = get_collision_point() + get_collision_normal()

It means that the returned collision point cannot be blamed for inaccurate results. So yeah it does smell like world_to_map() is not precise enough.

I've also looked at the source code for this method now, and stumbled upon this issue: #23250. So the proposed fix is indeed a hack currently. Your use case was described by @Zylann at https://github.com/godotengine/godot/issues/23250#issuecomment-435162860.

It means that the returned collision point cannot be blamed for inaccurate results.

Yes, exactly. In the Minimal project I moved the ColorRect to the collision point position to verify this. It's hundred percent accurate.

I've also skimmed over #23250 before writing this, but I did not understood the "hack" and assumed because it was closed, 23250 was about something else.

Maybe either Raycast2D or wold_to_map needs a bias argument for cases like this?
Personally I'm fine with using the collision normal. But I don't find coming to that conclusion intuitive at all. More like detective work.

@Xrayez thanks for the fix above, you just saved my life!

Was this page helpful?
0 / 5 - 0 ratings