can't edit UVs properly

Hello, I’m trying to generate UVs for a model by using the x and y positions of the vertices and the model’s bounding box to put the vertices in the UV space accordingly. Imagine if the bounding box becomes the UV grid and the y space will be ignored, that’s what I’m aiming for.

Here’s an image demonstrating what I’m trying to do:

I can’t get it quite right so far.

from panda3d.core import *
import direct.directbase.DirectStart

def regenerateUVs(np):
	bounds = np.getTightBounds() # [bottomleftXZY, toprightXZY]
	height = bounds[1][1] - bounds[0][1]
	width = bounds[1][0] - bounds[0][0]
	
	geomNodeCollection = np.findAllMatches('**/+GeomNode')
	for nodePath in geomNodeCollection:
		geomNode = nodePath.node()
	
		for i in range(geomNode.getNumGeoms()):
			geom = geomNode.modifyGeom(i)
			vdata = geom.modifyVertexData()
			
			texCoord = GeomVertexWriter(vdata, 'texcoord')
			vertex = GeomVertexReader(vdata, 'vertex')
			
			while not vertex.isAtEnd():
				vertexPos = np.getMat().xformPoint(vertex.getData3f())
				texCoord.setData2f(vertexPos[0]/width, vertexPos[1]/height)
	

panda = loader.loadModel('panda-model')
panda.reparentTo(render)

regenerateUVs(panda)

Hmm… Doesn’t Panda use by default a Z-up coordinate system – should you not perhaps be using z-coordinates and z-height rather than y-coordinates and y-height?

# Example of creating a triangle from scratch
from panda3d.core import *
import direct.directbase.DirectStart

# GeomVertexArrayFormat
array = GeomVertexArrayFormat()
array.addColumn(InternalName.make('vertex'), 3, Geom.NTFloat32, Geom.CPoint)

format = GeomVertexFormat()
format.addArray(array)

vformat = GeomVertexFormat.registerFormat(format)
vdata = GeomVertexData('vdata', vformat, Geom.UHStatic)

# GeomVertexWriter
vertex = GeomVertexWriter(vdata, 'vertex')
vertex.addData3f(0,0,0)
vertex.addData3f(1,0,0)
vertex.addData3f(1,0,1)

# GeomTriangles GeomPrimitive
prim = GeomTriangles(Geom.UHStatic)
prim.addConsecutiveVertices(0, 3)
prim.closePrimitive()

# Geom
geom = Geom(vdata)
geom.addPrimitive(prim)

# GeomNode
gnode = GeomNode('gnode')
gnode.addGeom(geom)

# NodePath
nodePath = NodePath(gnode)
nodePath.reparentTo(render)
nodePath.setTwoSided(True)

# to see the generated triangle
base.cam.setY(-6)

run()

Yep. I changed that on purpose but don’t remember why I had to do that. I thought the NodePath matrix caused it but my manual matrix multiplication didn’t fix it.

So is the problem solved now?

No, I just explained why I was using y-up instead of z-up space.

Then isn’t the fix simply to use this:

height = bounds[1][2] - bounds[0][2] 
width = bounds[1][0] - bounds[0][0] 
...
texCoord.setData2f(vertexPos[0]/width, vertexPos[2]/height) 

instead of this?

height = bounds[1][1] - bounds[0][1] 
width = bounds[1][0] - bounds[0][0] 
...
texCoord.setData2f(vertexPos[0]/width, vertexPos[1]/height) 

I’m guessing that getTightBounds() doesn’t respect NodePath matrices or something similar.
While it does seem to work with the default panda-model egg, it doesn’t on most others.

To illustrate the problem, let’s a apply a gradient texture like this:


The very top of the model should be completely black and the very bottom completely white.

from panda3d.core import *
import direct.directbase.DirectStart

def regenerateUVs(np):
   bounds = np.getTightBounds() # [bottomleftXZY, toprightXZY]
   height = bounds[1][2] - bounds[0][2]
   width = bounds[1][0] - bounds[0][0]
   
   geomNodeCollection = np.findAllMatches('**/+GeomNode')
   for nodePath in geomNodeCollection:
      geomNode = nodePath.node()
   
      for i in range(geomNode.getNumGeoms()):
         geom = geomNode.modifyGeom(i)
         vdata = geom.modifyVertexData()
         
         texCoord = GeomVertexWriter(vdata, 'texcoord')
         vertex = GeomVertexReader(vdata, 'vertex')
         
         while not vertex.isAtEnd():
            vertexPos = vertex.getData3f() # np.getMat().xformPoint(vertex.getData3f()) # no difference
            texCoord.setData2f(vertexPos[0]/width, vertexPos[2]/height)
   

panda = loader.loadModel('panda-model')
panda.reparentTo(render)

panda.setTexture(loader.loadTexture('gradient.png'), 1)

regenerateUVs(panda)

With panda-model, is good:

With my monkey-head egg:
2shared.com/file/ZW2PN0Bj/monkey.html

Ah, I just realised that you don’t do any effort to make the coordinates start at (0, 0). So, any positional coordinate of (-1, -1, -1) will also be mapped to a negative UV coordinate. You’ll need to calculate an additional offset to add to the vertices.

The separation between white and black is probably right at the XY plane.

Well I can get that info from the BoundingBox, but there seems to be more than that.

You may also have a bit of a scaling issue: your monkey-head’s top looks as though it has a UV of less than 0.5. As for the bottom half, I’m inclined to guess that you may have a texture wrap-mode effect producing the solid blackness in that region.

No, that’s part of the same reason. If the z coordinates are between -300 and 300, then the height will be 600, and so according to your formulae the UV range will be from -0.5 to 0.5 instead of 0 to 1.

You need to subtract bounds[0] from the result, like:

u = (vertexPos.x - bounds[0].x) / width
v = (vertexPos.z - bounds[0].z) / height
texCoord.setData2f(u, v)

Please test your code with my posted model. It doesn’t change much.

After applying my change and adding np.flattenLight() to the beginning of your regenerateUVs function it works for me.

This is because the individual nodes have transformations which you weren’t taking into account. flattenLight is a way of baking the transformation onto the vertices, so that there’ll be no transformation to take into account.

Well that was my assumption in my 3rd post.

For my case I can’t just bake the matrices. I tried calculating the final vertex positions like this but it doesnt seem right:

vertexPos = np.getMat().xformPoint(vertex.getData3f())

Any ideas?

That just gets the transform at the root node. You need to get the transform at the individual GeomNode level, relative to np’s parent (since your bounds are relative to np’s parent as well).

In your for loop where you iterate over geomNodeCollection, use this at the beginning to compute the matrix transformation between np’s parent and the geom node you’re currently processing:

xform = nodePath.getMat(np.getParent())

Then use xform.xformPoint(vertexPos) to transform the point.

(If you want to use any other coordinate system than np.getParent(), then you should keep in mind to transform the results of np.getBounds() into that coordinate system as well.)

The model had a single piece of GeomNode, but I guess it used the ModelRoot’s pivot, huh?
Thanks.