- Home /
Find UV coordinates of mesh without a raycast
I have a scene with a set of very simple procedurally generated meshes (flat, with four vertices each). Each of the meshes has a texture drawn on it.
I also have a player character that moves around underneath these meshes. While the scene is technically in 3D, it is effectively 2D, with a top-down, orthographic camera and no perspective.
What I would like to do is, given the player position, to get the UV coordinates of the point on the mesh the player is currently under. Figuring out if the player is under a specific mesh hasn't been that hard (using a C# version of http://wiki.unity3d.com/index.php/PolyContainsPoint), but I can't wrap my head on how to grab the UV coordinates of the mesh.
I know that traditionally you would simply raycast upwards to the mesh and then get the UV coordinates from the raycasthit. However, this isn't an option because I can't attach mesh colliders to these meshes (they deform in real-time and continually updating the mesh colliders is too expensive).
I would think this would be possible to do without a raycast, given that everything is effectively 2D, and I know the positions of all the vertices on the mesh, the texture UVs, and the player position. I've found some guidance on the forums for the inverse of my problem (converting UV coordinate on a mesh into world coordinates), but nothing for my specific problem (converting world coordinates into UV coordinates on a mesh).
Any help would be appreciated!
Answer by Bunny83 · Dec 01, 2015 at 02:54 PM
Well, that's quite easy given your restrictions of 2d orthographic projection without rotation. The easiest way is to use barycentric coordinates. All you need i have already posted over here. Given that all your coordinates are within the same plane (just set z = 0 for all positions) you can simply use GetBarycentric() with the vertex positions of the two triangles and use InTriangle to determine in which triangle the given point is. Once you have the barycentric coordinates of your player and know the triangle you can calculate the uv coordinates by simply using
Vector2 uv = Vert1uv * bary.x + Vert2uv * bary.y +Vert3uv * bary.z;
Of course you need to use the same vertex order you used in GetBarycentric.
edit
Here's a 3d version of GetBarycentric:
public static Vector3 GetBarycentric(Vector3 aV1, Vector3 aV2, Vector3 aV3, Vector3 aP)
{
Vector3 a = aV2 - aV3, b = aV1 - aV3, c = aP - aV3;
float aLen = a.x * a.x + a.y * a.y + a.z * a.z;
float bLen = b.x * b.x + b.y * b.y + b.z * b.z;
float ab = a.x * b.x + a.y * b.y + a.z * b.z;
float ca = a.x * c.x + a.y * c.y + a.z * c.z;
float cb = b.x * c.x + b.y * c.y + b.z * c.z;
float d = aLen * bLen - ab * ab;
Vector3 B = new Vector3((aLen * cb - ab * ca), (bLen * ca - ab * cb)) /d;
B.z = 1.0f - B.x - B.y;
return B;
}
It's actually just a couble of dot products, however to save method / operator calls i've written those manually so it's only native float math except for a, b and c.
second edit
I just packed everything into a struct. That way it's more clear that barycentric coordinates are not some kind of cartesian coordinates. I put it on the wiki. The "in triangle check" is now included and can be tested with a simple property. It also has an Interpolate method which simplifies the usage a little bit. I added constructors for Vector2, Vector3 and Vector4 values as well as Color values. So you can feed a reference color and in determines the position in the triangle based on the given vertex colors. Also note Vector4 is treated like a four-dimensional vector and not like "w" normalized Vector3
Hey Bunny83,
Thanks for the detailed answer (and for going through all that math!). I'm trying to implement your scripts and want to double-check a few things. Apologies in advance if I've misunderstood something in your explanation:
1) I just want to confirm that in GetBarycentric(), the first three inputs are the world space coordinates of the triangle vertices and the fourth input is the player's worldspace coordinates. I would run this in order to get the barycentric coordinates of the player in relation to the triangle.
2) $$anonymous$$y scene is effectively 2D but it is on the x-z plane, not the x-y plane. Will GetBarycentric() work if I modify the code to take Vector3's ins$$anonymous$$d of Vector2's (and replace the y coordinates with z coordinates)? How about InTriangle()? Would I just swap the y and z lines?
3) Just to confirm: the final formula in your explanation is simply the barycentric coordinates of the player (deter$$anonymous$$ed above) multiplied by the uv coordinates of the three vertices of the mesh triangle the player is underneath.
Thanks again.
Well, i've written the method as 2d variant since in the original question he wanted to do the reverse: Input UV cordinates, calculate the barycentric coordinates and finally calculate the worldspace coordinates. The method in it's current form only works with 2d coordinates. However since you have 2d coordinates that's not a problem. As you said just swap y and z if you use the x-z-plane. What space your coordinates are in doesn't matter as long as all coordinates are in the same space. So either convert your player position into local space and use the mesh vertices directly, or convert all vertices into world space and use the players worldspace position.
As mentioned you can simply swap y and z for world or local space coordinates. However barycentric coordinates always stay the same, so InTriangle will stay as it is. The x, y and z component of the baricentric coordinates have no relation to x, y and z axis. Each component belongs to a corner of your triangle. The value actually specifies the weight of that corner. The 3 components added together will always result in "1.0". If all 3 components are in the range 0 - 1 the specified point is inside the triangle.
Yes, you can use the barycentric coordinate to linearly interpolate any vertex parameter: position, normal, uv, tangent, color. As mentioned, each component is the weight of the corresponding corner.
It's possible to calculate the barycentric coordinates from 3d positions as long as all points are in the same plane. If i find the time i'll add a Vector3 variant. However since you only work in 2d, the 2d version requires less calculations ^^.
edit
ps: your post was stuck in the moderation queue. You also didn't post a comment but an answer. Answers should answer the question. All posts (questions and answers) have an "Add comment" button underneath and each comment has a reply button on top.
Thanks Bunny, first time forum user here so apologies on the formatting:-).
I put all the code together and everything works perfectly. I had done it before I saw your update with the struct, but for posterity here is my method. $$anonymous$$eshData is just a class I have to store all the data used to build the procedural meshes, and GetBarycentricXZ() is just my version of your Vector3 GetBarycentric():
void UVCheck($$anonymous$$eshData meshdata)
{
Texture2D tex = meshdata.$$anonymous$$aterial.mainTexture as Texture2D; //grabs the mesh's texture
Vector3 PlayerPosBarycentric = GetBarycentricXZ(meshdata.Vertices[0], meshdata.Vertices[1], meshdata.Vertices[2], Player.player_position); //deter$$anonymous$$e the player's barycentric coordinates relative to the first triangle of the mesh
if (InTriangle(PlayerPosBarycentric) == true) //if the barycentric coordinates are inside the triangle
{
Vector2 pixelUV = (meshdata.TextureUV[0] * PlayerPosBarycentric.x + meshdata.TextureUV[1] * PlayerPosBarycentric.y + meshdata.TextureUV[2] * PlayerPosBarycentric.z); //multiply the player's barycentric coordinates by the equivalent UV vertices to get the player's position on the UV map.
Color textureColor = tex.GetPixelBilinear(pixelUV.x, pixelUV.y); //grab the color of the UV point
Debug.Log(textureColor);
return; //stop method here
}
PlayerPosBarycentric = GetBarycentricXZ(meshdata.Vertices[2], meshdata.Vertices[3], meshdata.Vertices[0], Player.player_position); //deter$$anonymous$$e the player's barycentric coordinates relative to the second triangle of the mesh
if (InTriangle(PlayerPosBarycentric) == true) //if the barycentric coordinates are inside the triangle
{
Vector2 pixelUV = (meshdata.TextureUV[2] * PlayerPosBarycentric.x + meshdata.TextureUV[3] * PlayerPosBarycentric.y + meshdata.TextureUV[0] * PlayerPosBarycentric.z); //multiply the player's barycentric coordinates by the equivalent UV vertices to get the player's position on the UV map.
Color textureColor = tex.GetPixelBilinear(pixelUV.x, pixelUV.y); //grab the color of the UV point
Debug.Log(textureColor);
return; //stop method here
}
}
Your answer

Follow this Question
Related Questions
Mesh breaks apart when using vertex displacement with shadergraph. 3 Answers
Animated tiles and blending between different tiles on a mesh tilemap 0 Answers
Why can't I get a floating point value here?... 1 Answer
How to set up UV for lightmaps for generated meshes? 4 Answers
texturing a procedurally created mesh 0 Answers