Procedurally generated geometry and the default normals

Procedurally generated geometry: face normals are in the wrong orientation. How to fix without doing NodePath.setTwoSided()?

Actually this might not be automatically generated normals but something else. What determines which side of a polygon gets rendered in Panda by default?

If I recall correctly, it’s the “winding” of the face’s vertices–that is, when following them from first to last, which way the resulting loop runs.

Put another way, the fix is, I think, generally to reverse the order in which you’re assigning the vertices of the relevant faces.

The normals determine lighting, the winding order determines which side is on top (counter-clockwise by default).

To regenerate normals, you can use the egg-trans utility. The egg-trans -h command will show the options for regenerating the normals.

FWIW, usually you will want to compute the normal in a way that depends on that winding order, like in the following code:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase


class MyApp(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        # set up a light source
        p_light = PointLight("point_light")
        p_light.set_color((1., 1., 1., 1.))
        self.light = self.camera.attach_new_node(p_light)
        self.light.set_pos(5., -10., 7.)
        self.render.set_light(self.light)

        vertex_format = GeomVertexFormat.get_v3n3()
        vertex_data = GeomVertexData("triangle_data", vertex_format, Geom.UH_static)
        pos_writer = GeomVertexWriter(vertex_data, "vertex")
        normal_writer = GeomVertexWriter(vertex_data, "normal")
        # the following three points are listed in counter-clockwise order when
        # looking at the resulting triangle, in the direction of the positive Y-axis
        p1 = Point3(-1., 0., -.5)
        p2 = Point3(1., 0., -.5)
        p3 = Point3(0., 0., .5)
        pos_writer.add_data3(p1)
        pos_writer.add_data3(p2)
        pos_writer.add_data3(p3)
        v1 = p2 - p1  # vector pointing from p1 to p2
        v2 = p3 - p2  # vector pointing from p2 to p3
#        v3 = p1 - p3  # vector pointing from p3 to p1
        # the normal can be computed as the cross product of two out of the
        # above three side vectors:
        # v1 cross v2, v2 cross v3 or v3 cross v1
        normal = v1.cross(v2).normalized()
        # an easier alternative would be using a Plane, whose normal is equally
        # determined by that same winding order:
#        plane = Plane(p1, p2, p3)
#        normal = plane.get_normal()

        for _ in range(3):
            normal_writer.add_data3(normal)

        tris = GeomTriangles(Geom.UH_static)
        # add the vertex indices in the same counter-clockwise order as above
        tris.add_vertices(0, 1, 2)
        geom = Geom(vertex_data)
        geom.add_primitive(tris)
        geom_node = GeomNode("triangle")
        geom_node.add_geom(geom)
        self.triangle = self.render.attach_new_node(geom_node)


app = MyApp()
app.run()

Thanks all. I expected that in Panda the normals would be responsible both for shading as well as which side is “top” (rendered). Now I see what was the root of my problem.