Arnklit

  • Jan 27, 2021
  • Joined Dec 20, 2020
  • 0 best answers
  • How can I get the UV Coordinate of a world coordinate raycast hit? For example, when I click with the mouse in a mesh, I'd like to get the mesh's UV coordinate related to the click.

    The scenario is that the mesh can be anywhere, not only at the world's origin - and consider it a properly UV mapped mesh.

    Findings so far

    • Unreal Engine has a function called "FindCollisionUV" -
      From the source code you can see they use BaryCentric:

      FVector BaryCoords = FMath::ComputeBaryCentric2D(BodySpaceLocation, Pos0, Pos1, Pos2);
      // Use to blend UVs
      UV = (BaryCoords.X * UV0) + (BaryCoords.Y * UV1) + (BaryCoords.Z * UV2);
    • Unity has RaycastHit-textureCoord https://docs.unity3d.com/ScriptReference/RaycastHit-textureCoord.html

    • For Godot, I found these two projects https://github.com/RodZill4/godot-material-spray and https://github.com/Bauxitedev/godot-texture-painter but they translate the world coordinates to UV in the shader code, using some Matrix transformations that I have no idea how it works, which is fine, I could just copy-paste the code. But the problem is that their shader code only works if the mesh is at 0,0,0. If the mesh is not in the world origin, the UV translation fails.

    • As an alternative solution to using Barycentric, I created a shader that projects the mesh into a UV Plane https://godotshaders.com/shader/uv-unwrap-and-normals-unwrapping-world-aligned/, but then the projected UV mesh is useless because I still need to translate a world coordinate into the UV space 😛

    The closest to UE's/Barycentric's solution I have so far is below (code adapted from thefryscorer/GodotPaintDemo), but then the BaryCentric function (cart2bary from Arnklit/WaterwaysDemo from @Arnklit) I'm using always returns a Vector3 way out of bounds (something like x=200+). So my is_point_in_triangle is the function that is broken.

    UPDATE 1: My implementation below works perfectly if the mesh is at 0,0,0. I just got positive results. Now to understand why it doesn't work if the mesh is not at the world's origin.

    Any points would be very appreciated. I'm stuck in this.

    # Get UV
    var raycast = space.intersect_ray(from, to, [self]);
    var uv = get_uv_coords(raycast.position, raycast.normal)
    
    # Implementation
    func _ready():
    	mesh = get_node("mesh").get_mesh()
    	meshtool = MeshDataTool.new()
    	meshtool.create_from_surface(mesh, 0)
    
    func equals_with_epsilon(v1, v2, epsilon):
    	if (v1.distance_to(v2) < epsilon):
    		return true
    	return false
    
    func get_face(point, normal, epsilon = 0.2):
    	for idx in range(meshtool.get_face_count()):
    		if !equals_with_epsilon(meshtool.get_face_normal(idx), normal, epsilon):
    			continue
    		# Normal is the same-ish, so we need to check if the point is on this face
    		var v1 = meshtool.get_vertex(meshtool.get_face_vertex(idx, 0))
    		var v2 = meshtool.get_vertex(meshtool.get_face_vertex(idx, 1))
    		var v3 = meshtool.get_vertex(meshtool.get_face_vertex(idx, 2))
    		if is_point_in_triangle(point, v1, v2, v3):
    			return idx
    	return null
    
    func cart2bary(p : Vector3, a : Vector3, b : Vector3, c: Vector3) -> Vector3:
    	var v0 := b - a
    	var v1 := c - a
    	var v2 := p - a
    	var d00 := v0.dot(v0)
    	var d01 := v0.dot(v1)
    	var d11 := v1.dot(v1)
    	var d20 := v2.dot(v0)
    	var d21 := v2.dot(v1)
    	var denom := d00 * d11 - d01 * d01
    	var v = (d11 * d20 - d01 * d21) / denom
    	var w = (d00 * d21 - d01 * d20) / denom
    	var u = 1.0 - v - w
    	return Vector3(u, v, w)
    
    func is_point_in_triangle(point, v1, v2, v3):
    	var bc = cart2bary(point, v1, v2, v3)
    	if bc.x < 0 or bc.x > 1:
    		return false
    	if bc.y < 0 or bc.y > 1:
    		return false
    	if bc.z < 0 or bc.z > 1:
    		return false
    	return true
    
    func get_uv_coords(point, normal):
    	# Gets the uv coordinates on the mesh given a point on the mesh and normal
    	# these values can be obtained from a raycast
    	var face = get_face(point, normal)
    	if face == null:
    		return null
    	var v1 = meshtool.get_vertex(meshtool.get_face_vertex(face, 0))
    	var v2 = meshtool.get_vertex(meshtool.get_face_vertex(face, 1))
    	var v3 = meshtool.get_vertex(meshtool.get_face_vertex(face, 2))
    	var bc = cart2bary(point, v1, v2, v3)
    	var uv1 = meshtool.get_vertex_uv(meshtool.get_face_vertex(face, 0))
    	var uv2 = meshtool.get_vertex_uv(meshtool.get_face_vertex(face, 1))
    	var uv3 = meshtool.get_vertex_uv(meshtool.get_face_vertex(face, 2))
    	return (uv1 * bc.x) + (uv2 * bc.y) + (uv3 * bc.z)
    ```