Looks like we all had more or less the same idea. Cool !
Here’s the starship + hangar model for you to play around with. It’s a work in progress.
Thanks !
There didn’t appear to be any vertex colors applied to the starship model yet, so I decided to play around a bit with Vertex Paint in Blender myself… and I got things to work the way I wanted! As I don’t know how to make a Blender script, I had to apply the colors manually, so I only colored a small part of the model, but at least now I know that my script works as intended .
By the way, can color values in Blender’s color picker interface be displayed in the [0, 255]
integer range instead of the [0., 1.]
float range? And if there’s a way to more easily increment a color value than dragging a slider or typing in the new value (e.g. pressing +
-key or something), I’d love to hear it too!
All in all, I have to admit that the latest Blender versions (I used version 2.91) have indeed become a lot more intuitive, as I can actually figure some things out now without having to “RTFM” !
Here is the result of my mucking about with vertex colors (only the left rear of the main ship hull is done):
hangar_1_cont4.zip (2.2 MB)
Updated creation script:
#!/usr/bin/env python
from panda3d.core import *
import os
import array
loader = Loader.get_global_ptr()
loader_options = LoaderOptions(LoaderOptions.LF_no_cache)
models = []
model_names = []
original_format = GeomVertexFormat.get_v3n3t2()
# define custom vertex format with float color column
vertex_format = GeomVertexFormat()
array_format = GeomVertexArrayFormat()
array_format.add_column(InternalName.get_vertex(), 3, GeomEnums.NT_float32, GeomEnums.C_point)
array_format.add_column(InternalName.get_normal(), 3, GeomEnums.NT_float32, GeomEnums.C_normal)
array_format.add_column(InternalName.get_texcoord(), 2, GeomEnums.NT_float32, GeomEnums.C_texcoord)
array_format.add_column(InternalName.get_color(), 4, GeomEnums.NT_float32, GeomEnums.C_color)
vertex_format.add_array(array_format)
float_color_format = GeomVertexFormat.register_format(vertex_format)
# define custom vertex format with a separate int color column
vertex_format = GeomVertexFormat()
array_format = GeomVertexArrayFormat()
array_format.add_column(InternalName.get_vertex(), 3, GeomEnums.NT_float32, GeomEnums.C_point)
array_format.add_column(InternalName.get_normal(), 3, GeomEnums.NT_float32, GeomEnums.C_normal)
array_format.add_column(InternalName.get_texcoord(), 2, GeomEnums.NT_float32, GeomEnums.C_texcoord)
vertex_format.add_array(array_format)
array_format = GeomVertexArrayFormat()
array_format.add_column(InternalName.get_color(), 4, GeomEnums.NT_uint8, GeomEnums.C_color)
vertex_format.add_array(array_format)
int_color_format = GeomVertexFormat.register_format(vertex_format)
# collect all model names
for name in os.listdir("."):
if os.path.splitext(name)[1].lower() in (".bam", ".egg", ".fbx", ".gltf"):
model_names.append(name)
# load all models
for name in model_names:
for node in model.find_all_matches("**/+GeomNode"):
node.node().modify_geom(0).modify_vertex_data().format = float_color_format
model = NodePath(loader.load_sync(name, loader_options))
model.flatten_strong() # bake the transforms into the vertices
for node in model.find_all_matches("**/+GeomNode"):
models.append(node)
tmp_data_views = []
tmp_prim_views = []
sorted_indices = []
vert_count = 0
prim_vert_count = 0
# process all models
for model in models:
geom = model.node().modify_geom(0)
v_data = geom.get_vertex_data()
tmp_v_data = GeomVertexData(v_data)
tmp_v_data.format = float_color_format
data_size = tmp_v_data.get_num_rows()
tmp_data_view = memoryview(tmp_v_data.arrays[0]).cast("B").cast("f")
tmp_data_views.append(tmp_data_view)
tmp_v_data.format = int_color_format
color_array = tmp_v_data.arrays[1]
color_view = memoryview(color_array).cast("B")
prim = geom.modify_primitive(0)
prim.set_index_type(GeomEnums.NT_uint32)
prim_size = prim.get_num_vertices()
prim.offset_vertices(vert_count, 0, prim_size)
prim_view = memoryview(prim.get_vertices()).cast("B").cast("I")
tmp_prim_views.append(prim_view)
for j, color in enumerate(color_view[i:i+4] for i in range(0, len(color_view), 4)):
r, g, b, a = color
sort = r << 16 | g << 8 | b
sorted_indices.append((sort, vert_count + j))
vert_count += data_size
prim_vert_count += prim_size
sorted_indices.sort()
sort_values = [i[0] for i in sorted_indices]
sorted_indices = [i[1] for i in sorted_indices]
new_data = GeomVertexData("data", float_color_format, GeomEnums.UH_static)
data_array = new_data.modify_array(0)
data_array.set_num_rows(vert_count)
new_data_view = memoryview(data_array).cast("B").cast("f")
start = 0
for tmp_data_view in tmp_data_views:
end = start + len(tmp_data_view)
new_data_view[start:end] = tmp_data_view
start = end
new_data.format = original_format
tmp_prim = GeomTriangles(GeomEnums.UH_static)
tmp_prim.set_index_type(GeomEnums.NT_uint32)
prim_array = tmp_prim.modify_vertices()
prim_array.set_num_rows(prim_vert_count)
prim_view = memoryview(prim_array).cast("B").cast("I")
start = 0
for tmp_prim_view in tmp_prim_views:
end = start + len(tmp_prim_view)
prim_view[start:end] = tmp_prim_view
start = end
sorted_tris = []
for tri in (prim_view[i:i+3] for i in range(0, len(prim_view), 3)):
tri_indices = [sorted_indices.index(i) for i in tri]
sorted_tris.append((min(tri_indices), tri.tolist()))
sorted_tris.sort(reverse=True)
index, tri = sorted_tris.pop()
sort_val = sort_values[index]
tris = [tri]
tris_by_sort = [tris]
while sorted_tris:
index, tri = sorted_tris.pop()
next_sort_val = sort_values[index]
if next_sort_val == sort_val:
tris.append(tri)
else:
tris = [tri]
tris_by_sort.append(tris)
sort_val = next_sort_val
geom = Geom(new_data)
for tris in tris_by_sort:
new_prim = GeomTriangles(GeomEnums.UH_static)
new_prim.set_index_type(GeomEnums.NT_uint32)
prim_array = new_prim.modify_vertices()
tri_rows = sum(tris, [])
prim_array.set_num_rows(len(tri_rows))
new_prim_view = memoryview(prim_array).cast("B").cast("I")
new_prim_view[:] = array.array("I", tri_rows)
geom.add_primitive(new_prim)
geom_node = GeomNode("starship")
geom_node.add_geom(geom)
NodePath(geom_node).write_bam_file("starship.bam")
Updated application code:
from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
class Game(ShowBase):
def __init__(self):
ShowBase.__init__(self)
self.disableMouse()
# 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., -100., 7.)
self.render.set_light(self.light)
self.camera.set_pos(26.8519, 91.9869, 11.2998)
self.camera.set_hpr(157.174, -3.38226, 6.69603)
self.model = self.loader.load_model("starship.bam")
self.model.reparent_to(self.render)
bounds = self.model.node().get_bounds()
geom = self.model.node().modify_geom(0)
new_prim = GeomTriangles(GeomEnums.UH_static)
new_prim.set_index_type(GeomEnums.NT_uint32)
self.primitives = [prim for prim in geom.primitives]
self.primitives.reverse()
geom.clear_primitives()
geom.add_primitive(new_prim)
self.model.node().set_bounds(bounds)
self.model.node().set_final(True)
self.task_mgr.add(self.__build_starship, "build_starship", delay=0.5)
def __build_starship(self, task):
prim = self.primitives.pop()
prim_array = prim.get_vertices()
prim_view = memoryview(prim_array).cast("B").cast("I")
geom = self.model.node().modify_geom(0)
new_prim = geom.modify_primitive(0)
new_prim_array = new_prim.modify_vertices()
old_size = new_prim_array.get_num_rows()
new_prim_array.set_num_rows(old_size + len(prim_view))
new_prim_view = memoryview(new_prim_array).cast("B").cast("I")
new_prim_view[old_size:] = prim_view[:]
if self.primitives:
return task.again
game = Game()
game.run()
Note that I’ve set a delay of half a second before generating each next plate – otherwise the construction process would now actually be too fast!