PDA

View Full Version : I don't understand the "W" in "UVW"


plastic
12-11-2010, 03:19 PM
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:
http://666kb.com/i/bp57soayfmztv3lrw.png

Box with standard mapping, slightly rotated by UVW Xform modifier...result as expected:
http://666kb.com/i/bp57t61e58eqvmwak.png

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...
http://666kb.com/i/bp57tfs73h641olj0.png


(
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
)
)

PiXeL_MoNKeY
12-11-2010, 03:59 PM
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

Caprier
12-12-2010, 09:38 PM
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

plastic
12-14-2010, 10:02 AM
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


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)

Caprier
12-14-2010, 11:36 AM
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.

Klunk
12-14-2010, 09:04 PM
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.

Klunk
12-14-2010, 09:44 PM
sorry that should read

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

plastic
12-15-2010, 12:01 PM
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.

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.

plastic
12-15-2010, 12:25 PM
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


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:

http://666kb.com/i/bp95kmn5io3c5bo58.jpg

http://666kb.com/i/bp95qvnlezylt83cs.jpg

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:%\n" 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
)
)

CGTalk Moderation
12-15-2010, 12:25 PM
This thread has been automatically closed as it remained inactive for 12 months. If you wish to continue the discussion, please create a new thread in the appropriate forum.