Trying to optimize scene with lots of nodes

Since you are already generating your geometry procedurally, I can show you how to add and remove specific parts of a single model used for all of the roads.

The following code example creates a grid-like road setup (just simple rectangles) and allows you to randomly add and remove roads using the + and - keys, respectively:

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


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)

        # set up the road geometry
        vertex_format = GeomVertexFormat.get_v3n3t2()  # position, normal and uvs
        vertex_data = GeomVertexData("vertex_data", vertex_format, Geom.UH_static)
        tris_prim = GeomTriangles(Geom.UH_static)
        tris_prim.set_index_type(Geom.NT_uint32)
        geom = Geom(vertex_data)
        geom.add_primitive(tris_prim)
        geom_node = GeomNode("roads")
        geom_node.add_geom(geom)
        self.roads = self.render.attach_new_node(geom_node)

        # define the road dimensions
        length = 5.
        width = 2.

        self.roads.set_pos(-4. * length, 70., -10.)
        self.roads.set_p(30.)
#        tex = self.loader.load_texture("my_texture.png")
#        self.roads.set_texture(tex)

        # initialize road data
        self.road_count = 0
        self.road_ids = []

        # generate the roads
        for i in range(10):
            for j in range(10):
                pos1 = Point3((i - 1) * length, (j - 1) * length, 0.)
                pos2 = Point3((i - 1) * length, j * length, 0.)
                self.add_road(pos1, pos2, width)
                pos1 = Point3((i - 1) * length, (j - 1) * length, 0.)
                pos2 = Point3(i * length, (j - 1) * length, 0.)
                self.add_road(pos1, pos2, width)

        # enable randomly adding and removing roads at runtime
        self.accept("+", self.__add_random_road)
        self.accept("-", self.__remove_random_road)

    def add_road(self, pos1, pos2, width):

        values = array.array("f", [])
        tri_data = array.array("I", [])
        length_vec = pos2 - pos1
        normal = Vec3(0., 0., 1.)
        width_vec = length_vec.normalized().cross(normal) * width * .5
        points = (pos1 - width_vec, pos1 + width_vec,
                  pos2 - width_vec, pos2 + width_vec)
        uvs = ((0., 0.), (1., 0.), (0., 1.), (1., 1.))

        for point, uv in zip(points, uvs):
            values.extend(point)    # coordinates
            values.extend(normal)   # normal
            values.extend(uv)       # texture coordinates

        geom = self.roads.node().modify_geom(0)
        vertex_data = geom.modify_vertex_data()
        old_vert_count = vertex_data.get_num_rows()
        new_vert_count = old_vert_count + 4
        vertex_data.set_num_rows(new_vert_count)
        data_array = vertex_data.modify_array(0)
        memview = memoryview(data_array).cast("B").cast("f")
        # start the memoryview slice at the previous size, which equals
        # the old number of vertex rows multiplied by the number of float
        # values in each row (8: 3 for position, 3 for the normal and
        # another 2 for the texture coordinates)
        memview[old_vert_count * 8:] = values

        tris_prim = geom.modify_primitive(0)
        tris_array = tris_prim.modify_vertices()
        old_count = tris_array.get_num_rows()
        new_count = old_count + 6  # 2 triangles make up the road rectangle
        tris_array.set_num_rows(new_count)
        tri_data.extend((old_vert_count, old_vert_count + 1, old_vert_count + 3))
        tri_data.extend((old_vert_count, old_vert_count + 3, old_vert_count + 2))
        memview = memoryview(tris_array).cast("B").cast("I")
        memview[old_count:] = tri_data

        self.road_count += 1
        self.road_ids.append("road_{:d}".format(self.road_count))

    def remove_road(self, road_id):

        index = self.road_ids.index(road_id)
        self.road_ids.remove(road_id)

        geom = self.roads.node().modify_geom(0)
        vertex_data = geom.modify_vertex_data()
        start = index * 32  # 4 vertex rows per road; 8 float values per row
        end = start + 32
        old_vert_count = vertex_data.get_num_rows()
        new_vert_count = old_vert_count - 4
        data_array = vertex_data.modify_array(0)
        memview = memoryview(data_array).cast("B").cast("f")
        memview[start:-32] = memview[end:]
        vertex_data.set_num_rows(new_vert_count)

        tris_prim = geom.modify_primitive(0)
        start = index * 6  # 6 index rows per road; 1 int value per row
        end = start + 6
        old_count = tris_prim.get_num_vertices()
        new_count = old_count - 6
        tris_prim.offset_vertices(-4, end, old_count)
        tris_array = tris_prim.modify_vertices()
        memview = memoryview(tris_array).cast("B").cast("I")
        memview[start:-6] = memview[end:]
        tris_array.set_num_rows(new_count)

    def __add_random_road(self):

        values = array.array("f", [])
        r = random.random
        pos1 = Point3(r() * 50., r() * 50., r() * 2.)
        pos2 = Point3(r() * 50., r() * 50., r() * 2.)
        self.add_road(pos1, pos2, 2.)

    def __remove_random_road(self):

        road_id = self.road_ids[random.randint(0, len(self.road_ids) - 1)]
        self.remove_road(road_id)


app = MyApp()
app.run()

Instead of creating multiple nodes, a unique ID is generated for each road and stored in a list. Based on the index of the ID in the list, the start and end of the associated vertex data is calculated and this can be used to remove the geometry representing a particular road from the single model that includes all of the created roads.

Adding and removing geometry is done using memoryviews, so it should be quite efficient.
It’s a rather advanced way of dealing with geometry, but if you’re really into procedural geometry creation, it might be worth looking into.

2 Likes