Playing with UVWs... Need some help


I am stuck on something I thought would be trivial. I am trying to take the UVW of a face and format it into a specific structure to be exported as what is called Brush (or World) Geometry in the Source Game Engine.

A little background: I already have code for exporting Models that map each vertices UVW, so I already know how to get that kind of UVW data; the problem is with these world brushes because I cannot figure out how to translate the data I know how to get into the correct code for the world brushes (which does not map each vertex).

As I understand it, the brush geometry gets its UVW for each face from two lines of code that look like this:

“uaxis” “[1 0 0 0] 0.25”
“vaxis” “[0 1 0 0] 0.25”

Here is the documentation on this subject in the Valve Wiki ( ):

U/V Axis

The u-axis and v-axis are the texture
specific axes. They hold their own designations as they are independent of the
true x-axis and y-axis, but it’s easier to think of them in terms of x and y.
The u-axis represents the x-axis and the v-axis represents the y-axis. These two
code lines work together to define how the texture is displayed upon a face.

“uaxis” “[1 0 0 0] 0.25”
“vaxis” “[0 1 0 0] 0.25”

u/v axis ([x y z dec] dec)

The x, y, and z are decimal values representing that axis. The following value is the translation and the last decimal is the total scaling. The values for the x y z set represent the amount of the texture to be shown in that axis. They modify how much of the texture is shown within the normal space the texture would occupy on the face. A value of one means one repetition in the normal space, a value of two would mean two repetitions in the normal space. The key is that this only applies to one axis of the texture and is applied relative to true x, y, and z axes. That means that it is possible to set the texture to appear along an axis the plane doesn’t exist in. With a flat plane in the x-axis and the y-axis definitions are only needed for those axes as any definition in the z-axis has no surface to appear on. The code sample shows how this would be generated by Hammer. The texture’s x-axis (the u-axis) is displayed in the real x-axis and the texture’s y-axis (v-axis) is displayed in the real y-axis. Which of the x, y, and z axes are showing the axes of the texture depends entirely upon the faces orientation. Negative values will flip textures and Hammer will generate errors about textures shown in a plane the surface does not occupy. The second to last value is the simple translation of the textures origin within that axis; this value is actually a decimal though Hammer will round it up when displaying Hammer will still save the correct
decimal value. The last decimal is the overall scaling of the entire axis including what is defined in the x y z group.

I have tried various formulas that follow this logic:

 brushGeometry = ConvertToPoly (Box mapcoords:on)
for faceIndex = 1 to brushGeometry.numfaces do (
 face = polyop.getFaceVerts brushGeometry  faceIndex 
 in coordsys world 
  vert1 = polyop.getVert brushGeometry  face[1] node:brushGeometry 
  vert2 = polyop.getVert brushGeometry  face[2] node:brushGeometry 
  vert3 = polyop.getVert brushGeometry  face[3] node:brushGeometry  
 tvertFace = polyop.getMapFace brushGeometry  1 faceIndex 
 tv1 = polyop.getMapVert brushGeometry  1 tvertFace[1]
 tv2 = polyop.getMapVert brushGeometry  1 tvertFace[2]
 tv3 = polyop.getMapVert brushGeometry  1 tvertFace[3]
 format "###################
 I've tried both using the Verts...
 vaxis = normalize (vert2-vert1) 
 uaxis = normalize (vert2-vert3)
 format "Face % U axis using Verts: %
" faceIndex vaxis
 format "Face % V axis using Verts: %
" faceIndex uaxis
 And I've tried using the Texture Verts
 vaxis = normalize (tv2-tv1) 
 uaxis = normalize (tv2-tv3)
 format "Face % U axis using Texture Verts: %
" faceIndex vaxis
 format "Face % V axis using Texture Verts: %
" faceIndex uaxis
 And I've tried various random combinations of which verts to subtract to create the axis.
 None have consistently created the correct axis when viewed in Hammer/Source

I haven’t even yet tried to tackle the Translation and Scaling parameters in the output string for each axis as I can’t even get the U/V axises correct.

The problem has been getting more difficult because I am trying to make it so that whatever you see on a face in Max is how it exports into Source. And as I was struggling (unseccessfully) with the above problem, I also realized that I need to account for not only the actual UVW on the face, but any offsetting that is going on inside the texture–for example, if the Material on the Face has a Bitmap texture that is using the UVW Angles, Offset and Tiling.

Once I realized that I need to account for that, I started to think that the Angles, Offset and Tiling in the uaxis/vaxis are directly related to the texture’s coordinates (in the material editor) I started to think that my solution has to be something like this to output for the Source VMF format:

  • Get the X/Y Axis of the Face

  • Offset that by the UVW of the face

  • Offset that by the Materials Texture coordinates

  • Also get the Translation and tiling from the combined UVW/texture UVW

I may be wrong about that. But that is what I am thinking right now.

I am going to continue to experiment. But my personal problem is that although I have plenty or programming experience and can understand logic well… my math skills are weak. I have trouble thinking mathematically… and all of the matrix/vector aspect of this is extremely difficult and confusing for me. I’ve written to Valve for some help, but I’m not holding my breath in the meantime.

Any help would be greatly appreciated. I’ve mentioned my problem to several extremely smart MaxScript and SDK coders, and I am not actually the only one scratching my head–which is bad because I shouldn’t be doing anything to make my hair any thinner!


Mhhhh… does nobody have a clue?!


Good question :slight_smile: Still struggling with this.

There are a couple hints over on Steam… but the part that probably means the most is the code from Hammer’s perspective. Unfortunately, even with that, I’ve not found a way to reverse engineer that from Max’s side using UVW data.


I’m not much help in the uv-department, but this seems familiar anyway…

And I’ve tried various random combinations of which verts to subtract to create the axis. None have consistently created the correct axis when viewed in Hammer/Source

Have you tried this to get the two vectors:

 upVector = [0,0,1]
 fNormal = (polyop.getFaceNormal obj 1)
 u = normalize(cross fNormal upVector)
 v = normalize(cross fNormal u)


@kilD Thanks for the input. That definitely provides some useful info. I had seen that in the MAXScript docs a couple weeks ago and think it can utilized in the dilemma. But using just that info does not actually provide the final answer as there is some kind of offsetting that must be done to accomodate UVW coordinates… and I am missing a few fundamental concepts I am sure.

However, the more I’ve immersed myself in this problem, the more I think I’m starting to understand the underlying relationship of the mesh and the UVW mesh more than I did in the past… so at least that is good.

I think there is some trigonometry that needs to be applied… and unforunately… I spent three years in the same math class back in high school. Apparently, I was more interested in skipping math for hanging out with girls… because, obviously, no one ever needs to know math in the real world, right?! :slight_smile:

PS. Have to give a thanks to everyone trying to help me on this… especially Gulliver “K@rt” Thoday … I dragged him into the 3ds Max world a few months ago and now got him quagmired in this :slight_smile:


So I am continuing to struggle with this problem. Slowly… slowly this problem has been converting me from an undetermined quantum vector into a normalized point3 … :slight_smile: but honestly, this is playing far outside my comfort zone.

So I wanted to rephrase the problem in less verbose terms… as it might help my odds of enlisting a MAXScript god to shed some wisdom:

We have an Editable Poly… for each face we need to take the UVW information in Max and redefine it such that the same texture on that face will appear at the same angle, translation and scale when defined like this:

“uaxis” “[1 0 0 0] 0.25”
“vaxis” “[0 1 0 0] 0.25”

I have tried multiple combinations of creating the uaxis from the face verts, using the facenormal, normalized vectors defining the uaxis then using cross on the facenormal… then crossing the uaxis and facenormal to get the vaxis; I’ve tried again by using the texture vertices to do the same (making horrible results); I’ve tried creating matrix3 from the vertices and applying them as the coordinate system of the tvert axis creations… vice versa.

Etc etc etc.

And nothing outputs the correct UVW data for my target (the Source Game Engine). Some can “look” correct at moments… but nothing actually takes what you see for the textures placement, rotation and scale of a face in Max to the same thing inside Source.

I’m at a point that when the answer is revealed, my understanding of this all will probably be deepened immensely. But I simply haven’t got any mathematical foundation to solve this (it’s what you get when you convert a journalist into a programmer and 3D guy… I’m more like Mark Twain than Martin Gardner… which is really useful for making variable names in the correct local dialect… but isn’t great for solving math problems).

I beseech the great MAXScript gods to shed light on this mere mortal :slight_smile:


I really need to figure this out myself.
It seems like they are just defining a rectangle with zero translation, meaning it starts at bottom left at [0,0] and 1 means the tiling.
The final float for scaling would be bitmap and/or gizmo scaling.

“uaxis” “[1 0 0 0] 0.25”
“vaxis” “[0 1 0 0] 0.25”

 p = convertToPoly(plane widthsegs:1 lengthsegs:1)
 num = polyop.getNumMapVerts p 1
 verts = for i = 1 to num collect polyop.getMapVert p 1 i

the mapverts: #([0,0,0], [1,0,0], [0,1,0], [1,1,0])
corresponding to v1: bottom left, v2: bottom right, v3: top left, v4: top right

here’s testing it the other way:

 fn setUV node uAxis uScale vAxis vScale=
 	polyop.setMapVert node 1 1 [0,0,0]
 	polyop.setMapVert node 1 2 [uAxis[1]*uScale,0,0]
 	polyop.setMapVert node 1 3 [0,vAxis[2]*vScale,0]
 	polyop.setMapVert node 1 4 [uAxis[1]*uScale,vAxis[2]*vScale,0]
 	update node
 setUV p [1,0,0,0] 0.25 [0,1,0,0] 0.25

the translation part for uaxis would be (polyop.getMapVert node 1 1).x
same with vaxis, just y

After rotating the uv gizmo 45 degrees and then trying to recreate it, ofcourse it fails.
I just can’t see how those 2 lines can support any rotation.


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.