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.
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:
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.
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)
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.
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.)