Notifications
Clear all

[Closed] UV location to mesh location

I want to create a function that receives a mesh and a 2D coordinate as input and returns a point3 (maybe many) on the mesh where that 2D point lies, based on the UV coordinates of the mesh.

But it’s not so easy though, as the input isn’t at specific vertex locations, but anywhere in UV space. So it might or might not have any information at a given point. Not only that but it also can return multiple points, in case of overlapping faces in UV space.

In resume I’m looking for a function that looks like this: UV_To_Mesh_Sample theMesh theUV and returns an array of point3.

Regards,
Jr.

8 Replies

Look up Barycentric Coordinates in the Maxscript Help.

-Eric

Search for threads started by me with “uvw” in the title.
I got some half-working solution. The problem is that it only works with default uvw maps on primitives, not with a uvw map modifier, as far as I remember. The whole uvw thing in max is a nightmare, IMO.

1 Reply
(@bobo)
Joined: 11 months ago

Posts: 0

Not really, you can call snapshotAsMesh() to get the TriMesh of any object, the rest is trivial meshOp. mapping calls (there is a whole section in the help about accessing all 100 map channels in a mesh).

do it step by step…
step #1: can you make a function to check if some 2D point inside some 2D triangle?
we need this function to find all UV faces which contain the UV point.

Thanks a lot for the answer guys. But I’m still stuck in this problem…I did most of what you told me already, but as I didn’t want to get into the details of my tries to keep the post as short as possible.

I already did a function to find the Barycentric Coordinates. However that’s not my main problem.

  Suppose I have this part up and running. Like say, I pass the coordinates [0.3, 0.2, 0] in UVW space and I find 3 faces containing that point. Fine. But what I really need is where in 3D space the point [0.3, 0.2, 0] actually lies for each face. To my logic seems more like the inverse of the barycentric coordinates, something I don't know how to do.
  
  Another problem is that I'm going to have too many geometry queries, because at every step the script goes in 2D, I have to go through all triangles in the mesh to check if it falls inside a face or faces. Maybe there's an easier way of doing this, but I really can't see other than using acceleration structures and that is far more trouble this script deserves.

Actually if it gets too complicated I have to give up on the idea because there's no time to develop it...

[b]Purpose of the script:[/b] This is my reference: [ http://www.youtube.com/watch?v=rUFnH7KYMFE ]( http://www.youtube.com/watch?v=rUFnH7KYMFE). 

I have to do something similar, and I already got a nice procedural pattern, so I was looking for a method of wrapping it into arbitrary meshes. The best idea that came to mind was generating it in UV space and converting the values to 3D.

Regards,
Jr.

well here is what I got:

(
	delete $Point*

	local meshObj = snapShotAsMesh $
	local theChannel=1
	
	local stepsU=5 as Double 
	local stepsV=5 as Double
	local totalSteps=stepsU*stepsV

		--Point in triangle test...returns returns point2 value, or false if point outside.
		-- http://www.blackpawn.com/texts/pointinpoly/default.html 
	fn pointInTriangle a b c thisPoint =
	(
		local v1 = c-a
		local v2 = b-a
		thisPoint.z=0 --only interested in the 2d plane
		local v3 = thisPoint-a

		local dot11 = dot v1 v1
		local dot12 = dot v1 v2
		local dot13 = dot v1 v3
		local dot22 = dot v2 v2
		local dot23 = dot v2 v3
		
		local div = 1 / (dot11 * dot22 - dot12 * dot12)
		local u = (dot22 * dot13 - dot12 * dot23) * div
		local v = (dot11 * dot23 - dot12 * dot13) * div
		if (u >= 0) and (v >= 0) and (u + v <= 1) then [u,v] else 
		(
			--format "U:% V:%
" u v
			--[u,v]
			false
		)
	)
	
		--build array with steps grid
	fn buildStepsArray stepsU stepsV=
	(
		local counteri1=0
		local counteri2=0
		local UVSearch=#()
		for i1=0 to 1 by (1/stepsU) do
		(
			counteri1+=1
			for i2=0 to 1 by (1/stepsV) do
			(
				counteri2+=1
				local result=[i1,i2,0]
				append UVSearch result
			)
		)
		UVSearch
	)
	UVSearch=buildStepsArray stepsU stepsV
	
		--collect all faces
	local meshFaces=meshObj.faces as bitarray
		--collect all map verts for each face
	local theMapFaces=for f in meshFaces collect meshop.getMapFace meshObj theChannel f
		--collect UV coords of each face/vertex
	local theFaceVertsUVCoords=for v in theMapFaces collect #(meshop.getMapVert meshObj theChannel v.x, meshop.getMapVert meshObj theChannel v.y, meshop.getMapVert meshObj theChannel v.z)

	vertexPositions=#()
	for UVCoord in UVSearch do
	(
			--collect all affected faces
		local hitFaces=for f in theFaceVertsUVCoords collect PointInTriangle f[1] f[2] f[3] UVCoord --Check if point is in triangle
		for f=1 to hitFaces.count where hitFaces[f]!=false do
		(
			theFace = getFace meshObj f --gives a Point3 with 3 vertex indices
			v1=getVert meshObj theFace.x
			v2=getVert meshObj theFace.y
			v3=getVert meshObj theFace.z
			appendIfUnique vertexPositions (v1 * hitFaces[f].x +  v2 * hitFaces[f].y +  v3 * (1 - (hitFaces[f].y + hitFaces[f].x)))
		)
	)
	 with redraw off for i in vertexPositions do
	(
		point pos:i
	)
)

Result:
Grid of point helpers with 5 subdivs on U and V on a box with default mapping:

For a given UV coordinate, there are 6 points in world space, in this case (box mapping).

There is a problem with this code I couldn’t figure out.
When the original box is segmented, something strange happens, probably related to a rounding error? when uv point lies on a edge then the corresponding face cant be found…or something… :shrug:

Plastic, thanks a ton man! Although I was almost done, you still helped me. I finally managed to resolve my main issues! :bounce:
My code seems to be working nicely, but it’s still wip. I didn’t care about UV Tiling for now as I’m probably not going to need it. At least it respects correctly the mapping coordinates.

There are problems of course, but are not so apparent in the code below because I’m just creating point helpers at sample points, but as for my needs I must create a spline out of those, so they must be created in proper order otherwise the spline will look like a mess.

Now I need to think in a way to optimize it as as I said it goes through all the mesh for every sample point. Any suggestion is again very welcome…

And I’m doing a really nasty trick converting a float to string and the string back to float to get around a maxscript inaccuracy comparing a variable to 1.0. I’m not sure the best (and faster) solution for that.

To try out the code just copy it and create a new script and with some object selected run it.
NOTE: Coordinates with tiling (values out of 0-1 range) will not be taken into account.

(
	obj = snapShotAsMesh $
	theNumFaces = meshop.getNumFaces obj
	theWorldPos = #()
	step = 10.0

	--Find Barycentric Coordinates Function
	fn find_barycoords_fn obj theFace theCoord =
	(
		--Gets the UVW vertices of the given face
		theFaceVerts = getTvFace obj theFace
		
		--Gets the UVW position of each face vertex.
		--NOTE: No value is needed for the W coordinate, so the multiplication cancels it out.
		v1 = getTVert obj theFaceVerts[1] * [1, 1, 0]
		v2 = getTVert obj theFaceVerts[2] * [1, 1, 0]
		v3 = getTVert obj theFaceVerts[3] * [1, 1, 0]
		
		--Calculates the edges of the internal triangles
		edge1 = theCoord - v1
		edge2 = theCoord - v2
		edge3 = theCoord - v3
		
		--Calculates the total area of the parallelogram formed by the triangle
		ta = length (cross (v1-v2) (v3-v2))
		
		a1 = cross edge1 edge3
		a2 = cross edge2 edge3
		a3 = cross edge2 edge1
		
		bc1 = length (a1/ta)
		bc2 = length (a2/ta)
		bc3 = length (a3/ta)
		
		theTotal = (bc1 + bc2 + bc3) as string
		
		--If the sum of the three components of the barycentric coordinates is bigger than one it means the point is outside the triangle
		if theTotal as float <= 1.0 then
			bc = [bc1, bc2, bc3]
		else
			bc = undefined
		
	)
	
	for v=0 to 1 by 1/step do
		for u=0 to 1 by 1/step do
			for f=1 to theNumFaces do
			(
				--Returns the barycentric coordinates in UV space for a given UVW point
				bc = find_barycoords_fn obj f [u, v, 0]
				
				--Gives a Point3 with 3 vertex indices
				theFace = getFace obj f
				
				v1=getVert obj theFace[2]
				v2=getVert obj theFace[1]
				v3=getVert obj theFace[3]
				
				if bc != undefined then
					appendIfUnique theWorldPos (v1 * bc.x +  v2 * bc.y +  v3 * (1 - (bc.y + bc.x)))
				
			)
	
	pt = point()
	for i in theWorldPos do
	(
		newPt = instance pt
		newPt.pos = i
		newPt.wireColor = green
	)
	delete pt
)

Hey Eugenio,
I’m looking forward trying your code but I’m on vacation right now and cant test it with my laptop. Will give it a run in a few days.
For the comparing float issue, you may try using the closeEnough() function, instead of a “==” compare.
Also try converting your numerical values to “Double” precision instead of the default “Float”.