Generating model files programmatically

In a perfect world, every content creation tool would be able to import and export in one common format that can easily be converted to the native data representation of a game engine. But because we do not live in such a perfect world, there are literally hundreds to thousands of file formats for storing 3D meshes. What makes things worse is the fact that there might be no import plugin for our engine or content creation tool available, forcing us into writing our own converter, which is very often the case with brand new tools and file formats.

This recipe aims to be an exercise in writing our own custom file format conversion utility. We will be working on an arbitrary set of vertex data, converting it to Panda3D's internal format, and saving the data into a file.

Getting ready

This recipe extends the basic application skeleton described in Setting up the game structure found in Chapter 1. Please set up a new project according to these instructions before going on. Also, prepare a texture image in PNG format.

How to do it...

This recipe consists of these tasks:

  1. Create a new subdirectory called textures in the project directory.
  2. Rename your texture file to texture.png and copy it to the textures directory.
  3. Open Application.py and add the following import statements:
    from panda3d.core import *
    
  4. Extend the constructor of the Application class:
    class Application(ShowBase):
    Application classaboutdef __init__(self):
    ShowBase.__init__(self)
    self.generateEgg()
    model = loader.loadModel("generated")
    model.reparentTo(render)
    dirLight = DirectionalLight("directional")
    dirNode = render.attachNewNode(dirLight)
    dirNode.setHpr(20, 20, 20)
    render.setLight(dirNode)
    self.cam.setPos(5, -5, -5)
    self.cam.lookAt(model)
    
  5. Add the following method to Application class:
    def generateEgg(self):
    eggRoot = EggData()
    meshGroup = EggGroup("Mesh")
    vertexPool = EggVertexPool("Vertices")
    eggRoot.addChild(vertexPool)
    eggRoot.addChild(meshGroup)
    vertices = (Point3D(-1, 1, 1),
    Point3D(-1, -1, 1),
    Point3D(1, -1, 1),
    Point3D(1, 1, 1),
    Point3D(1, 1, -1),
    Point3D(1, -1, -1),
    Point3D(-1, -1, -1),
    Point3D(-1, 1, -1))
    texcoords = (Point2D(0, 1),
    Point2D(0, 0),
    Point2D(1, 0),
    Point2D(1, 1))
    faces = ((0, 1, 2, 3),
    (4, 5, 6, 7),
    (7, 6, 1, 0),
    (3, 2, 5, 4),
    (7, 0, 3, 4),
    (1, 6, 5, 2))
    texture = EggTexture("color", Filename("../textures/texture.png"))
    for face in faces:
    Panda3Dmodel files, generatingpolygon = EggPolygon()
    meshGroup.addChild(polygon)
    for index, uv in zip(face, texcoords):
    vertex = vertexPool.makeNewVertex(vertices[index])
    vertex.setUv(uv)
    polygon.addVertex(vertex)
    polygon.addTexture(texture)
    polygon.recomputePolygonNormal()
    polygon.triangulateInPlace(True)
    
  6. Launch the program. You should see something comparable to the following screenshot:
How to do it...

How it works...

In the generateEgg() method, we can see a very common set of data to process. We have a vertex buffer, an index buffer for referencing vertices, and a set of texture coordinates as well as a texture image. All of which somehow need to go into an .egg file.

Panda3D's .egg files use a tree structure containing various nodes for geometry, textures, and materials, among others. The root of this tree is represented by the EggData class, to which other child nodes might be added. In our sample, we add a group node and a vertex pool.

The group node is used to store the polygons our model consists of, while all the vertices forming the polygons have to be part of a vertex pool. We can think of this data structure as a simple list of vertices. We can use multiple meshes and vertex pools to form a model, but all vertices used to build a mesh have to be part of the same vertex pool. In our sample we add the vertices that form our mesh to a single pool. Additionally, we assign each vertex a texture coordinate so Panda3D will be able to put a texture map onto the surface of our mesh.

We add a texture image to each of the newly created polygons. In our case, this is only one texture, but each subsequent call to addTexture() adds another texture layer to a polygon. We also calculate the polygon normal because we need that for lighting to work in Panda3D.

The algorithms and hardware involved in rasterized real-time rendering work most efficiently when processing triangles. In comparison to other geometrical forms, triangles are very simple and their properties are very well known. In addition, any polygonal surface can be split into triangles, which makes them a perfect format for describing arbitrary meshes. So to allow the graphics hardware to work most efficiently, we split the cube's six surface planes into twelve triangles.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset