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.