To create a triangle procedurally, you start off by choosing a vertex format; this format determines what kind of attributes your triangle vertices can have, such as:
- a position (this is always needed);
- a lighting normal for correct shading of the triangle;
- texture coordinates;
- a color (not often needed, especially if you’re going to use textures, materials and/or a “scenegraph color”);
- tangent-space vectors (needed if you want to apply a normal map to your geometry);
- some custom attributes, often for use with shaders.
For the following small tutorial, let’s use a simple, predefined format which allows you to define position and normal for each vertex:
vertex_format = GeomVertexFormat.get_v3n3()
Now you need to build up a table of vertex data, filled with rows containing the values for each of those aforementioned attributes: the GeomVertexData.
vertex_data = GeomVertexData("triangle_data", vertex_format, Geom.UH_static)
To add the rows, you can use a GeomVertexWriter for each attribute of the three triangle vertices:
pos_writer = GeomVertexWriter("vertex")
normal_writer = GeomVertexWriter("normal")
normal = Vec3(0., -1., 0.)
pos_writer.add_data3(-1., 0., -1.)
pos_writer.add_data3(1., 0., -1.)
pos_writer.add_data3(0., 0., 1.)
# since the normal is the same for each vertex, just add it three consecutive times
for _ in range(3):
normal_writer.add_data3(normal)
The normal in this case is very straightforward: it points in the direction of the negative Y-axis. With more complicated shapes, you’ll need to compute it, using two vectors pointing from one vertex to another.
Now that you have the data, you need to build up geometry structures to make use of it.
First there’s the GeomPrimitive, specifically GeomTriangles:
prim = GeomTriangles(Geom.UH_static)
Now comes an important step: you need to add the vertices (actually the indices of the vertex data rows) in counter-clockwise winding order to the primitive, as seen when looking straight at the side of the triangle that you want to be rendered (the other side will be “culled”, i.e. not rendered). In this example, that index order would be (0, 1, 2), i.e. the order the rows were added to the vertex data table. That makes it possible to do:
prim.add_next_vertices(3)
It’s not always that easy, though, and then you’ll need to be more explicit, like so:
prim.add_vertices(0, 1, 2)
Phew
! Finally you need to put that primitive inside a Geom, add that Geom to a GeomNode and wrap that GeomNode into a NodePath that can ultimately be attached to the scenegraph:
geom = Geom(vertex_data)
geom.add_primitive(prim)
node = GeomNode("my_triangle")
node.add_geom(geom)
triangle = NodePath(node)
triangle.reparent_to(some_other_nodepath)
That’s it
!
If you want more info or some better understanding of that winding order, please check out my proposed new geometry samples (start with basic.py
), which are an attempt to improve upon the outdated sample that @Moguri just linked to and which is actually not a very good sample, for several reasons.