Panda3D Cube Geometry

Following function creates a GeomNode representing a simple cube with texture coordinates, colors, and normals at each vertex. I have a template for the texture map that corresponds to the texture coordinates but nowhere to post it. PM me if you want it …

Enjoy!
Paul

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Function name : CreateCube
#
# Description:
#
#	Create a Panda3D GeomNode representing a simple unit cube and include
#   texture coordinates, colors, and normals at each vertex.
#
# Input(s):
#
#   nodeName - Name for the GeomNode (string)
#   color - Color for the cube (list/tuple of 4 values)
#
# Output(s):
#
#	A GeomNode representing a unit icosahedron.
#
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def CreateCube(nodeName,color):

	# Define the vetex data format (GVF). 
	format = GeomVertexFormat.getV3n3c4t2()

	# Create the vetex data container (GVD) using the GVF from above to
	# describe its contents.
	vdata = GeomVertexData(nodeName+'GVD',format,Geom.UHStatic)

	# Create writers for the GVD, one for each type of data (column) that
	# we are going to store in it.
	gvwV = GeomVertexWriter(vdata, 'vertex')
	gvwT = GeomVertexWriter(vdata, 'texcoord')
	gvwC = GeomVertexWriter(vdata, 'color')
	gvwN = GeomVertexWriter(vdata, 'normal')

	# Upload the model info to the GVD using the writers
	gvwV.addData3f(-0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.26500, 0.67500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.49000, 0.97500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.26500, 0.97500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.26500, 0.67500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.49000, 0.67500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.49000, 0.97500)
	gvwN.addData3f( 0.00000,-0.00000, 1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.26500, 0.35000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.49000, 0.65000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.26500, 0.65000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.26500, 0.35000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.49000, 0.35000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.49000, 0.65000)
	gvwN.addData3f( 0.00000,-1.00000,-0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.73500, 0.35000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.51000, 0.65000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.51000, 0.35000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.73500, 0.35000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.73500, 0.65000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.51000, 0.65000)
	gvwN.addData3f( 1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.75500, 0.35000)
	gvwN.addData3f( 0.00000, 1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.98000, 0.65000)
	gvwN.addData3f( 0.00000, 1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.75500, 0.65000)
	gvwN.addData3f( 0.00000, 0.00000,-1.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.75500, 0.35000)
	gvwN.addData3f( 0.00000, 1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.98000, 0.35000)
	gvwN.addData3f( 0.00000, 1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.98000, 0.65000)
	gvwN.addData3f( 0.00000, 1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.02000, 0.65000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.24500, 0.35000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000, 0.50000)
	gvwT.addData2f( 0.24500, 0.65000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000, 0.50000)
	gvwT.addData2f( 0.02000, 0.65000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.02000, 0.35000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.24500, 0.35000)
	gvwN.addData3f(-1.00000, 0.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.49000, 0.32500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.26500, 0.02500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.49000, 0.02500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f( 0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.49000, 0.32500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000,-0.50000,-0.50000)
	gvwT.addData2f( 0.26500, 0.32500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	gvwV.addData3f(-0.50000, 0.50000,-0.50000)
	gvwT.addData2f( 0.26500, 0.02500)
	gvwN.addData3f( 0.00000,-1.00000, 0.00000)
	gvwC.addData4f(color[0],color[1],color[2],color[3])

	geom = Geom(vdata)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(0)
	tris.addVertex(1)
	tris.addVertex(2)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(3)
	tris.addVertex(4)
	tris.addVertex(5)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(6)
	tris.addVertex(7)
	tris.addVertex(8)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(9)
	tris.addVertex(10)
	tris.addVertex(11)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(12)
	tris.addVertex(13)
	tris.addVertex(14)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(15)
	tris.addVertex(16)
	tris.addVertex(17)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(18)
	tris.addVertex(19)
	tris.addVertex(20)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(21)
	tris.addVertex(22)
	tris.addVertex(23)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(24)
	tris.addVertex(25)
	tris.addVertex(26)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(27)
	tris.addVertex(28)
	tris.addVertex(29)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(30)
	tris.addVertex(31)
	tris.addVertex(32)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	tris = GeomTriangles(Geom.UHStatic)
	tris.addVertex(33)
	tris.addVertex(34)
	tris.addVertex(35)
	tris.closePrimitive()
	geom.addPrimitive(tris)
	node = GeomNode('Cube')
	node.addGeom(geom)
	return node

nice one, thx for shareing… but sometimes… i wonder what loops where made for… :unamused: just kidding

greetings
thomasegi

ps: it’s less the cube but much more the usage how to simply create geometry during runtime. nice example:)

Sure I could have done it with a loop but this was generated code. I created a python app that reads a wavefront OBJ file and creates a GeomNode with its contents. I made a lot of simplifications to get the basic geometry done and its not quite ready for public consumption yet. I might post that converter when its completed.

[edit]

The converter doesn’t create a GeomNode, it creates python code that creates a GeomNode such as that above.

How do you specify all those vertex points in world coordinates?

Say if I have a box that I want to specify of size 300 x 300 x 300 pixels, how would it change?

Every time I do an addVertex3f(0,0,300), it blows up. Can you specify a point the point that way?

meant to say addData3f(0,0,300)

Certainly there are no restrictions on the numerical values you can supply for a vertex; if you wanted to create a cube 300 units on a side you can certainly say addData3f(0, 0, 300) and it should work just fine. I don’t know what you might be referring to when you say “it blows up”.

Of course, it might be easier just to take the cube built exactly as above, and simply call model.setScale(300).

David

I apologize for not being a little more clear.

So right now, I am using the procedural cube example in the panda sample program to create a cube.

After the cube is created, I would like to apply a video texture to each face of the cube.

The procedural cube example uses a similar approach as the example provided above, using GeomVertexData and GeomNode to specify vertices and triangles, then add new nodes to be rendered in the scene graph.

Is there a way to access each Geom within a GeomNode so that I can apply a different video texture (such as the pandasneeze video texture) to each face?

What I had to do to get this working was create a new GeomNode for each face in the procedural cube example. In the example of procedural cube, there is only one GeomNode, and 6 Geoms which represent the different face. I basically created 6 different GeomNode each having 1 Geom.

I guess the question really comes down to, can you apply different textures to multiple Geoms within a GeomNode? In the case of the procedural cube example, a tree texture is applied to each face. Can I have a tree texture on one face and another texture on another face by directly changing Geoms within a GeomNode.

The default panda code is:

square0=makeSquare(-1,-1,-1, 1,-1, 1)
square1=makeSquare(-1, 1,-1, 1, 1, 1)
square2=makeSquare(-1, 1, 1, 1,-1, 1)
square3=makeSquare(-1, 1,-1, 1,-1,-1)
square4=makeSquare(-1,-1,-1,-1, 1, 1)
square5=makeSquare( 1,-1,-1, 1, 1, 1)
snode=GeomNode(‘square’)
snode.addGeom(square0)
snode.addGeom(square1)
snode.addGeom(square2)
snode.addGeom(square3)
snode.addGeom(square4)
snode.addGeom(square5)

cube=render.attachNewNode(snode)

  .............

self.testTexture=loader.loadTexture(“maps/envir-reeds.png”)
cube.setTexture(self.testTexture)

which applies the envir-reeds texture to the whole cube…

Thanks

It’s possible. The easiest way to do set this up is to create six different GeomNodes, as you say you have already done, and then call model.flattenStrong(), which should flatten them all into a single GeomNode.

Note, though, that the six different faces are still six separate Geoms, and must still be rendered with six separate calls–because each face has a different texture, and therefore a different render state–so in terms of performance it doesn’t help much to flatten them down into a single node.

David

I have searched aroung and this seems to be the most appropriate thread for my question, even though it is a bit old.

I am creating geometry procedurally (it will be only done once, at the beginning of each game - so generation time is unimportant, rendering performance is more critical).

I have a matrix of values which represent the height of buildings. For each building face I do the following:
(1) draw two triangles (GeomTriangles object with 4 points)
(2) add them to a Geom
(3) add the Geom to the GeomNode that represents the city.

I chose this method because I read here that

I can’t seem to find out how to actually apply a texture to a Geom though. In this screenshot I have applied a texture to the whole GeomNode, but I want to be able to give each building face it’s own texture.

Can anybody help? Or must I do it the way David suggests, creating many GeomNodes and then flattening them?

Also, any comments on the rendering efficiency of this solution would be appreciated. I have read in other places that one should not send more than 300 “batches” to the graphics card, does a single Geom count as a batch?

Thanks!

To answer your question specifically, it is possible to assign a unique texture to each Geom. This is done when you add the Geom to the GeomNode; the GeomNode.addGeom() call takes two parameters, where the first parameter is the Geom and the second parameter is the RenderState object that represents the unique state for this Geom.

A RenderState is a collection of individual RenderAttribs. In your case, it might just contain an individual TextureAttrib.

All of this is very low-level stuff and not meant to be directly manipulated by user code, so there aren’t a lot of convenience functions to do it. This is why it’s usually easier to create many GeomNodes and flatten them together, because then you can use the convenience methods of NodePath to apply the textures, and at the end of the day the result is the same.

Now, to answer your second question, this is a terrible approach for efficiency. Each Geom does indeed count as a batch. When you’re constructing a city, you really want to have many buildings within a single Geom, rather than four or five Geoms per building. Fortunately, flattenStrong() can combine many Geoms into a single Geom, but only if they share the same texture (and all other RenderState attributes). So if you apply a different texture to each of the four walls of a building, you’re still S.O.L.

But there is still a solution. One easy solution is egg-palettize. This is a tool that combines multiple textures into a single texture, and shifts the UV coordinates of the vertices to compensate. But egg-palettize is meant to run offline, by the developer; not at runtime. But since you’re doing this only at startup, this may be an option for you: instead of constructing your city every time the game launches, construct it once, and store it in an egg file. Then use egg-palettize to process that egg file, which will optimize it for multiple textures; and then just load up the egg file when you launch your game.

If you want every game to start with a randomized building height, you can still solve that too. Just generate the egg file with every building having a height of 1.0 (or some standard height), and then when you load the model, scale all the buildings according to your random settings.

If you really insist on generate the buildings completely at runtime, instead of preloading them from an egg file, you can still solve this too, but in this case you need to run egg-palettize on a set of blank cards that will represent your building faces. One easy way to generate these cards is with “egg-texture-cards *.png” in your textures directory. egg-palettize will still process all of these cards, and optimize them into a single texture for you; then when you construct your buildings, instead of using GeomVertexWriter to construct a quad for each face of the building, you will load up the appropriate card model and arrange them all together. And now flattenStrong() will be able to successfully combine faces, because egg-palettize ensures that they are all sharing the same texture.

Incidentally, I would say that your question really deserves its own thread, rather than piggybacking on the back of someone else’s slightly related thread. Don’t be afraid to create a new thread when you have a new question. :slight_smile:

David

Just wanted to say, thanks for the detailed reply, I really appreciate your time.

I will try implement it and let you know the results.

Perhaps I should have made a new thread, I also wondered that after posting. I just hate searching and finding a million threads with tiny snippets of information, I always try and make sure my questions will be useful to someone after me who might have searched for the same thing.