RenderState problems

Hi,

What I want to do is create a hierarchy of 2 box models, where the child model (box2) does not inherit the render state of the parent model (box1). To this end, box2 has a RenderState that turns off those attributes (in my actual project, I do take care to leave on those also used by the child node itself). Specifically, box1 has two textures applied. So I call set_texture_off(tex_stage) on box2 for the 2 corresponding texture stages used for box1, and this seems to work well.
Then I write the hierarchy to a .bam file. But when I load this .bam file, it can happen that the second texture shows up on box2, which is not what I want. The strange thing is that this doesn’t always happen; sometimes it looks correct, so there seems to be some randomness involved.

Here is the code that creates the hierarchy and saves it out to a .bam file:

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


def create_cube(name):

    vertex_format = GeomVertexFormat.get_v3n3t2()
    vertex_data = GeomVertexData("cube_data", vertex_format, Geom.UH_static)
    tris = GeomTriangles(Geom.UH_static)

    pos_writer = GeomVertexWriter(vertex_data, "vertex")
    normal_writer = GeomVertexWriter(vertex_data, "normal")
    uv_writer = GeomVertexWriter(vertex_data, "texcoord")

    vert_count = 0

    for direction in (-1, 1):

        for i in range(3):

            normal = VBase3()
            normal[i] = direction

            for a, b in ((-1., -1.), (-1., 1.), (1., 1.), (1., -1.)):

                pos = VBase3()
                pos[i] = direction
                pos[(i + direction) % 3] = a
                pos[(i + direction * 2) % 3] = b

                pos_writer.add_data3f(pos)
                uv_writer.add_data2f((a, b))
                normal_writer.add_data3f(normal)

            vert_count += 4

            tris.add_vertices(vert_count - 2, vert_count - 3, vert_count - 4)
            tris.add_vertices(vert_count - 4, vert_count - 1, vert_count - 2)

    geom = Geom(vertex_data)
    geom.add_primitive(tris)
    node = GeomNode(name)
    node.add_geom(geom)

    return node


class MyApp(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        root = self.render.attach_new_node(ModelRoot("hierarchy"))
        box1 = root.attach_new_node(create_cube("box1"))
        box1.set_scale(2.)
        box1.set_pos(1., 2., 0.)
        box2 = box1.attach_new_node(create_cube("box2"))
        box2.set_scale(.5, 1., 1.5)
        box2.set_pos(2., -7., 5.)

        # Apply two textures to box1.

        tex_stage1 = TextureStage("tex_stage1")
        tex_stage2 = TextureStage("tex_stage2")
        texture1 = self.loader.load_texture("my_texture1.jpg")
        box1.set_texture(tex_stage1, texture1)
        texture2 = self.loader.load_texture("my_texture2.png")
        box1.set_texture(tex_stage2, texture2)

        # Make sure that the textures applied to box1 do not show up on box2.

        box2.set_texture_off(tex_stage1)
        box2.set_texture_off(tex_stage2)

        dir_light = DirectionalLight('directional_light')
        self.dir_light_np = self.cam.attach_new_node(dir_light)
        self.dir_light_np.set_hpr(-20., -20., 0.)
        self.render.set_light(self.dir_light_np)
        self.cam.set_pos(20., -25., 25.)
        self.cam.look_at(0., 0., 0.)

        # create the .bam file
        root.write_bam_file("hierarchy.bam")


app = MyApp()
app.run()

And here is the code that loads the .bam file:

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


class MyApp(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        np = NodePath("test")
        np.set_color_off()
        state = np.get_state()
        color_attrib = state.get_attrib(ColorAttrib.get_class_type())
        print "Show vertex colors:", color_attrib.get_color_type() == ColorAttrib.T_vertex

        hierarchy = self.loader.load_model("hierarchy.bam")
        hierarchy.reparent_to(self.render)
        tex_attr_type = TextureAttrib.get_class_type()

        box1 = hierarchy.find("**/box1")
        state = box1.get_state()
        tex_attrib = state.get_attrib(tex_attr_type)

        for i in range(tex_attrib.get_num_on_stages()):
            tex_stage = tex_attrib.get_on_stage(i)
            print "box1 texture stage on:", tex_stage.get_name()

        box2 = hierarchy.find("**/box2")
        state = box2.get_state()
        net_state = box2.get_net_state()

        print "\nRender state on box2 itself:"
        tex_attrib = state.get_attrib(tex_attr_type)
        print "    number of texture stages off:", tex_attrib.get_num_off_stages()
        print "    number of texture stages on:", tex_attrib.get_num_on_stages()

        for i in range(tex_attrib.get_num_off_stages()):
            tex_stage = tex_attrib.get_off_stage(i)
            print "    texture stage off:", tex_stage.get_name()

        print "\nRender state on box2 from root:"
        tex_attrib = net_state.get_attrib(tex_attr_type)
        print "    number of texture stages off:", tex_attrib.get_num_off_stages()
        print "    number of texture stages on:", tex_attrib.get_num_on_stages()

        for i in range(tex_attrib.get_num_on_stages()):
            tex_stage = tex_attrib.get_on_stage(i)
            print "    texture stage on:", tex_stage.get_name()

        dir_light = DirectionalLight('directional_light')
        self.dir_light_np = self.cam.attach_new_node(dir_light)
        self.dir_light_np.set_hpr(-20., -20., 0.)
        self.render.set_light(self.dir_light_np)
        self.cam.set_pos(20., -25., 25.)
        self.cam.look_at(0., 0., 0.)


app = MyApp()
app.run()

The box1 model is the cube at the right.
When you run this code multiple times, you should see that box2, at the left, either appears white (as expected), or with the second texture that was applied to box1.
(The same happens when using pview.)

As you can gather from the output, the render state on box2 itself is always correct, in that it turns off both texture stages used by box1:

box1 texture stage on: tex_stage1
box1 texture stage on: tex_stage2

Render state on box2 itself:
    number of texture stages off: 2
    number of texture stages on: 0
    texture stage off: tex_stage1
    texture stage off: tex_stage2

But the net render state of box2 can either incorrectly turn on a texture stage (the second one):

Render state on box2 from root:
    number of texture stages off: 0
    number of texture stages on: 1
    texture stage on: tex_stage2

or not:

Render state on box2 from root:
    number of texture stages off: 0
    number of texture stages on: 0

Another, minor issue, is that calling NodePath.set_color_off() creates a ColorAttrib whose color type is T_vertex instead of T_off, as one might expect.

By the way, is calling box2.get_state().compose(box1.get_net_state()) equivalent to box2.get_net_state()?