Notifications
Clear all

[Closed] I don't understand the "W" in "UVW"

I’m working on a way to use the UV mapping information for regularly distributing objects on a poly surface, independently of the underlying triangulation/segments.

I’m having success, as long as I use the original mapping information, generated by 3dsmax, when creating the objects.
But as soon as I use the “UVW Map” modifier, things no longer work, because the mapping is different. For example, with a plane primitive, the UV map is a planar projection going from U=0…1 and V=0…1. W is always zero. (As I expect it, with planar projection).
When I put a new planar UVW map on top, then it’s no longer as expected, but there are also W coordinates involved. I’m converting the UV coordinates to barycentric coordinates, so I have no clue what to do with those W coordinates, and why they are here in the first place.
When using the mapping for a bitmap, both versions look the same of course. What I’m trying to achieve is nothing different than projecting a bitmap. (Grid of pixels vs grid of world coordinates)

There are many old threads where people looked for that functionality and gave up. I’m happy that I got it working to some degree, but it would be cool if it also worked with the “UVW Map” modifier.
I’d be happy for any input.

Box with standard mapping:

Box with standard mapping, slightly rotated by UVW Xform modifier…result as expected:

UVW Map modifier on top with box projection. Should be the same as the first pic? But it’s not what I’m expecting and I don’t understand what’s going on here…


(
	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
	function SameSide p1 p2 a b = dot (cross (b-a) (p1-a)) (cross (b-a) (p2-a))>=0
	function PointInTriangle a b c p = (SameSide p a b c) and (SameSide p b a c) and (SameSide p c a b)

		--Alternative Point in triangle test...returns returns point2 value, or false if point outside.
		-- http://www.blackpawn.com/texts/pointinpoly/default.html 
	fn pointInTriangle2 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 false	
	)
			--fast version, only valid if point really in triangle?
	fn buildBarycentricCoordinates a b c p =
	(	
		local v1 = p-a
		local v2 = p-b
		local v3 = p-c
		local a1= (length(cross v2 v3))/2
		local a2= (length(cross v3 v1))/2
		local a3= (length(cross v1 v2))/2
		local ta= a1+a2+a3
		[a1/ta, a2/ta, a3/ta]	
	)
		--Bobo´s long version
	fn buildBarycentricCoordinates2 v1 v2 v3 p=
	(
		local vector1 = p - v1		   --this is the vector from vertex 1 to the dummy
		local vector2 = p - v2		   --this is the vector from vertex 2 to the dummy
		local vector3 = p - v3		   --this is the vector from vertex 3 to the dummy

		--Calculate the cross product of the 3 vectors
		local theCross1 = (cross vector2 vector3)
		local theCross2 = (cross vector3 vector1)
		local theCross3 = (cross vector1 vector2)

		--calculate the face normal using the cross product of two edges
		local theNormal = normalize (cross (v2-v1) (v3-v1))

		--because the length of the cross product vector is equal to the area of the parallelogram
		--defined by the two operands, half of it is the area of the triangle!
		local area1 = (length theCross1 )/2  --this is the area of the first sub-triangle
		local area2 = (length theCross2 )/2  --this is the area of the second sub-triangle
		local area3 = (length theCross3 )/2  --this is the area of the third sub-triangle

		--calculate the angle of each cross product with the face normal
		local angle1 = acos ( dot ( normalize theCross1) theNormal )
		local angle2 = acos ( dot ( normalize theCross2) theNormal )
		local angle3 = acos ( dot ( normalize theCross3) theNormal )

		--if the angle is different from the other two, take its area as negative
		if angle1 != angle2 and angle1 != angle3 then area1 = -area1
		if angle2 != angle1 and angle2 != angle3 then area2 = -area2
		if angle3 != angle1 and angle3 != angle2 then area3 = -area3

		local fullArea = area1 + area2 + area3 --this is the full area of the triangle

		local b1 = area1 / fullArea  --this is the proportion of the first triangle vs. the full triangle
		local b2 = area2 / fullArea  --this is the proportion of the second triangle vs. the full triangle
		local b3 = area3 / fullArea  --this is the proportion of the third triangle vs. the full triangle

		[b1, b2, b3]  --Behold! Hand-made Barycentric coordinates!!!l
	)
	
		--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
		(
			a=theFaceVertsUVCoords[f][1]
			b=theFaceVertsUVCoords[f][2]
			c=theFaceVertsUVCoords[f][3]
			baryCoords = buildBarycentricCoordinates a b c UVCoord
			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*baryCoords.x +  v2*baryCoords.y +  v3*baryCoords.z)
		)
	)
	
	for i in vertexPositions do
	(
		point pos:i
	)
)

8 Replies

You can do cubic/UVW mapping with the planar. I do it all the time manually by switching to box, setting the height value and switching back. So you should be able to get/set the height parameter of the planar map. For best results I would use XYZ to UVW mapping as it will map the world coordinates to UVW for you.

-Eric

For example, with a plane primitive, the UV map is a planar projection going from U=0…1 and V=0…1. W is always zero. (As I expect it, with planar projection).

W is zero not because of planar mapping but because the mapped object is a plane.
If you apply a planar mapping to an object you also get W values according to the object’s vertical dimensions. It’s like rescaling the object so that its bounding box fits in a 1x1x1 units cube.
The Edit UVWs dialog is kind of a viewport with three orthographic views:
UV <=> top
VW <=> right
UW <=> front

1 Reply
(@plastic)
Joined: 11 months ago

Posts: 0

OK I understand that.
Where I’m stuck is this: Each (bitmap) texture is 2d/XY/UV. As long as the mapping goes UV without W (W=0), then it’s no problem to convert the map coordinates to surface UV coordinates using baycentric calculations, point in triangle, etc. (as with my code above).

But when there is a W value I have no clue how 2d mapping works in this case. Bitmaps get projected, so there must be some method to project bitmap XY to UVW, but how?

With my code above I just ignored W (always 0), but it’s obviously wrong. (3rd image)

If I understand correctly, in your 3rd image you’re using box mapping. In this case it’s like a planar mapping applied from the three directions only to the polys facing that direction.
For the top and bottom polys, you get U <=> X and V <=>Y.
For the ones perpendicular to the X axis, it’s U <=>Y and V <=> Z.
And for the last ones, U <=> X and V <=> Z.
(With one of the two sides flipped)

If what you’re trying to do is go from UVW space to XYZ, you might want to try the unwrap interface. It has a very useful method called getVertexGeomIndexFromFace() that returns the vertex indices of a geometric face from the corresponding texture face. (It can get very messy if working on a polymesh instead of a trimesh though, depending on the number of verts per poly)
From there, computing a position on a texture face from the UVs and finding the corresponding position on the object face is relatively easy. But you need to add an unwrap modifier.

1 Reply
(@plastic)
Joined: 11 months ago

Posts: 0

Hi Caprier,

Thanks, I’m going to experiment with getVertexGeomIndexFromFace()
I’m pretty close to achieve what I want (UV to XYZ worldcoords) without using the UnwrapUVW modifier though.

I don’t know why I’m doing this because you didn’t read/listen to what I posted last time. It has nothing to do with the UVW , W is always assumed to be zero so has no effect on the outcome

change

local hitFaces=for f in theFaceVertsUVCoords collect PointInTriangle f[1] f[2] f[3] UVCoord --Check if point is in triangle

to

local hitFaces=for f in theFaceVertsUVCoords collect PointInTriangle2 f[1] f[2] f[3] UVCoord --Check if point is in triangle

change

appendIfUnique vertexPositions (v1*baryCoords.x +  v2*baryCoords.y +  v3*baryCoords.z)

to

appendIfUnique vertexPositions (v1 * hitFaces[f].x +  v2 * hitFaces[f].y +  v3 * (1 - (hitFaces[f].y + hitFaces[f].x)))
  

you can delete

a=theFaceVertsUVCoords[f][1]
  			b=theFaceVertsUVCoords[f][2]
  			c=theFaceVertsUVCoords[f][3]
  			baryCoords = buildBarycentricCoordinates a b c UVCoord

altogether. FYI the u,v in the pointintriangle2 routine are the barycentric coords as you only have to compute 2 (1 – (u +v)) gives you the third.

1 Reply
(@plastic)
Joined: 11 months ago

Posts: 0

Hi Claude,
Believe me, I did read what you posted.
The problem is, I do not always understand everything. I’m an architect, not a programmer.
What I’m doing is messing around with different methods and functions until it works.
When I’m done I only understand half of what’s going on, even if I wrote everything. Sad but true.

Anyway, thanks for your corrections!
I’m getting the same result now with original mapping and UVW modifier.

There is one issue left though, I didn’t have with my old code:

I think the problem is in this line:

if (u >= 0) and (v >= 0) and (u + v <= 1) then [u,v] else false

Maybe a precision problem? I’m not sure…

New version, almost working:

(
	delete $Point*

	local meshObj = snapShotAsMesh $
	local theChannel=1
	
	local stepsU=10 as Double 
	local stepsV=10 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
	)
)

sorry that should read

appendIfUnique vertexPositions (v1 * (1 - (hitFaces[f].y + hitFaces[f].x)) +  v2 * hitFaces[f].y +  v3 * hitFaces[f].x)