Collaborative Sci-Fantasy Tech Demo for an Official Panda3D Showcase

Looking neat thus far! The stripes of sunlight are a nice touch. :slight_smile:

[edit]
All right, here is the blend-file and the Blender script-file that I mentioned above!

Note well, however!

  • This was created with Blender 2.78c.
  • Further, the egg-file posted above was exported with a modified version of YABEE.
    • I think that the only relevant modification is that my version of YABEE exports the first channel of the second vertex-colour layer as the alpha-channel of the first vertex-colour layer. However, I wouldn’t like to swear to there being no other applicable modifications.

That said, here are the files!
constructionTest.blend (773.4 KB)
generateConstructionColours.zip (1.3 KB)
(The zip-file should hold the Blender Python-script, as Python file uploads are not allowed.)

Thank you for the explanation :slight_smile: .

Agreed on both points! Shaders can make things easier to accomplish, but they are not exclusive to Panda3D, so for this particular demo it is indeed better to stick with procedural generation (which I prefer anyway :wink: ).

Fair enough!

Yes, I noticed that you use alpha values to determine the order in which major parts of the model are to be generated, and that is certainly a good idea :slight_smile: . But in case it’s not possible to use this approach for the final version of the model, exporting those parts as appropriately-numbered separate files would work just fine, so don’t worry about it!

And thank you for your work as well :slight_smile: !

Very very nice :slight_smile: !

OK, so here is my own first little contribution: a Python script that looks for any models in the current directory and assembles them into one single model (with the triangles added in the desired order), as well as a small application to test the progressive generation of the starship.

Script to produce the starship.bam file:

#!/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 = []

# 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:
    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)

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)

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 << 32 | g << 16 | b << 8 | a
        sort = a * 255 * 255 + r + g + b
        sorted_indices.append((sort, vert_count + j))

    vert_count += data_size
    prim_vert_count += prim_size

sorted_indices.sort()
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()
sorted_tris = [t[1] for t in sorted_tris]
tris = array.array("I", [])

for tri in sorted_tris:
    tris.extend(tri)

new_prim = GeomTriangles(GeomEnums.UH_static)
new_prim.set_index_type(GeomEnums.NT_uint32)
prim_array = new_prim.modify_vertices()
prim_array.set_num_rows(prim_vert_count)
new_prim_view = memoryview(prim_array).cast("B").cast("I")
new_prim_view[:] = tris

geom = Geom(new_data)
geom.add_primitive(new_prim)
geom_node = GeomNode("starship")
geom_node.add_geom(geom)
NodePath(geom_node).write_bam_file("starship.bam")

It will need to be adjusted in order to work with multiple numbered files and the vertex format of the resulting model is very basic at the moment, but that will be amended later on. Another thing I’ll need to change is to make it possible to generate the model quad-by-quad instead of triangle-by-triangle.
But it seems like a good start.

And here is main.py:

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

class Game(ShowBase):

    def __init__(self):

        ShowBase.__init__(self)

        self.disable_mouse()

        # 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(5, -5, 7)
        self.camera.set_hpr(45, -45, 0)

        self.model = self.loader.load_model("starship.bam")
        self.model.reparentTo(self.render)

        bounds = self.model.node().get_bounds()
        geom = self.model.node().modify_geom(0)
        prim = geom.get_primitive(0)
        self.prim_view = memoryview(prim.get_vertices()).cast("B").cast("I")
        new_prim = GeomTriangles(GeomEnums.UH_static)
        new_prim.set_index_type(GeomEnums.NT_uint32)
        geom.set_primitive(0, new_prim)
        self.model.node().set_bounds(bounds)
        self.model.node().set_final(True)
        self.prim_row_index = 0
        self.task_mgr.add(self.__build_starship, "build_starship", delay=0.)

    def __build_starship(self, task):

        start = self.prim_row_index
        end = start + 3
        geom = self.model.node().modify_geom(0)
        prim = geom.modify_primitive(0)
        prim_array = prim.modify_vertices()
        prim_array.set_num_rows(end)
        prim_view = memoryview(prim_array).cast("B").cast("I")
        prim_view[start:end] = self.prim_view[start:end]
        self.prim_row_index += 3

        if self.prim_row_index != len(self.prim_view):
            return task.again


game = Game()
game.run()

Very slick code. I was able to immediately get some results with this. The starship.bam creation script seems to work well combining my .gltf files, and main.py runs without errors (well, without textures as well, but that can be resolved later). I’ll post back once I have made progress with a more true to scale starship model.

As my hangar model suggests, I would like to make the starship fairly large, maybe 100 meters long and wide enough to just fit out the hangar door. I’ll start making asset and code posts so that other artists can improve my bare-bones stuff later.

1 Like

That’s very reassuring to hear! Some time ago I tried to load a .gltf file myself with a similar script and for some reason it gave an error (don’t remember what it was exactly), so I’m quite relieved that it works for you :slight_smile: .

No worries, texture maps aren’t being processed by my creation script yet – will definitely try to remedy this soon.

Sounds great :slight_smile: !

1 Like

Very cool! It looks like the script works well! :slight_smile:

… I didn’t find any clearly-appropriate models with which to test it, so I just tested it on an agglomeration of low-poly characters… ^^;

Hmm… I have an idea: if we want to reinforce the idea that this is procedural generation, why not have the ship be remade in multiple arrangements before finally settling on its proper form?

In short, what I’m suggesting is this:

  • Take various ship-parts and partway assemble them in some more-or-less random arrangement
    • Likely not fully random, but randomly placed within reason–i.e. in a fashion that still looks like a ship.
    • The pool of ship-parts would include more parts than the final ship uses.
  • At some randomly-defined point, cancel the assembly, and remove the ship.
  • Repeat a few times
  • And once that’s done, start assembling the ship in its final form, and this time complete it.

There’s an argument to be made for using a randomly-assembled ship for the final form, too, come to that. It might be neat if the player gets to fly something different each time!

Neat idea! I’m afraid this may end up being impractical for higher-poly meshes. But even there, we could probably do a handful of “hull meshes” to throw around the same interior ship models – and maybe randomize these being applied a bit in Section 1 (though, for the rest of the demo, I’d probably want to keep the ship the same for ergodic reasons).

Here’s my initial ship design. Voice opinions early and often to have the maximum impact in how this demo turns out! I’m willing to change around my design ideas, to a point.

The current ship is a 100 meter SSTO with 2 main decks and a partially split command deck.

(from Blender)

(in Panda being generated)

Things I’d like to note about the current procedural gen code:

  • The speed varies by the polygon count. I’d like to have control over this, ideally, because to make impressive models I’d like to have a big polygon budget. We need to be able to time it for practical reasons; the current ship takes about 10 minutes to “generate”. Lower-poly models render gen much faster.
  • My .gltf parts are rendered in black only.
  • I’m using set_two_sided() on the starship.bam gen file, which causes some shadow/light acne, but it still looks better overall because the generated geoms block out the sunlight (cool) but it only looks right if you can see both sides of the geometry.
  • We should have a way of specifying which parts get generated first, like doing the landing gear first makes more logical sense.
  • The pattern of generation seems to change based on how I literally arrange the polygons as I’m working on the model file. This is fine for me, but it may be unrealistic to expect the generator to always have a nice clean wrap-around effect with in-order polygons. It works quite well in this regard with polygon meshes which are not being actively edited and re-loaded.

I would think that it shouldn’t differ that much from the original proposal: In both cases, the ship is built up out of various parts. It’s just that in my proposal above, the parts are randomly chosen and placed.

Now, the procedural ship might have more parts, depending on how the generation goes–but I wouldn’t think that it would end up with so many more that it would become more a problem than would the original proposal.

Hmm… That’s an awfully long time–too long for a showcase, I fear.

I suppose that the thing to do is to determine where that time is being spent. Perhaps there’s something that might be optimised, or even moved to C++ code if called for.

In my “first draft”, above, this is done by specifying an “order” value. I stored this in the alpha channel of the model’s vertex-colours, but it could also be set as a separate shader-input, or Python-tag, or some such.

Alternatively, one could envisage a system in which each section of the ship is placed into a list, and the generation pulls from that list. As a result, each section would be generated in the order in which they were added to the list.

I modified your shader code slightly to accept my .gltf files and was able to get this result:

I will defer to Epihaius with regard to speeding up their procedural generation code. At this point, it is a working solution, but only for a low-poly ship, which does not fit the project outline.

I would be happy to make this project compatible with .egg or older Blender builds, with the caveat that I won’t be able to support things like modified YABEE systems myself.

Well, my YABEE-exported sample above was really just a first draft, intended somewhat as a proof-of-concept of the general approach; it wasn’t intended as a full solution. Hence my initial hesitation at posting the Blender-file and Blender-script!

I presume that we’ll be going with something closer to Epihaius’ work, actually constructing the geometry anew, rather than my shader. Said shader was primarily made so that I could display what I was doing in the Blender-script, the latter being the main thing being demonstrated, as I recall.

Great :slight_smile: !

An additional idea: maybe the player character could walk into the hangar, look at the ship being constructed and say something like:

Wait, it’s Saturday! How can I possibly fly around in something looking like that today!

Upon which he pulls out his PDA or whatever and chooses a “Change ship layout” option, at which point the player can switch between random designs by pressing the arrow keys (each time a key is pressed, the building process restarts).
This way the player has a bit more control over what happens – and can prolong or cut short the rebuilding shenanigans as desired.

Looks really great!

Those black stripes/hexagons, are they shadow artifacts? Or are they polygons generated separately from the rest of the mesh? The latter shouldn’t be happening… but I don’t know how you are applying vertex colors to the model in Blender.

What I myself don’t like about the generation algorithm is that you can see separate triangles being generated that are attached to the previously generated geometry at only a single vertex. This looks weird. The solution to this – and to the issue of the ship generation in general taking too long – is to define groups of triangles that can all be generated at once. In fact, what I envisioned originally was for a robot/drone to not “materialize” singular triangles, but rather entire “hull plates”. (On a side note, to further enhance the impression of energy being converted into matter, these could initially appear as a glowing, semi-transparent object that gradually darkens and becomes fully opaque as it “solidifies”. Note that this process shouldn’t take long – one or at most two seconds should suffice, I’d say. Anyway, we can discuss this later.)
The only way I see to make this possible – and thereby fixing the aforementioned problems – is to assign the same vertex color to all of the vertices that make up such a “hull plate”.
Today I actually tried to combine the triangles of Thaumaturge’s constructionTest.egg into quads, based on corresponding color, but surprisingly this only succeeds for a handful of triangles :frowning: . Perhaps this is due to rounding errors or lack of numerical precision in the .egg file, I’m not sure.

If you guys agree that generating larger “plates” instead of separate triangles is the way to go (it could likely also look more realistic), then some way (Blender script?) needs to be found to ensure that each plate gets its own unique vertex color assigned.
If feasible, the following workflow in Blender might work:

  • select the vertices of the first “plate” (adjacent to a model seam);
  • assign the current color (initially black) to them;
  • increment the current color, e.g. like this in a Blender script:
    color_value = r << 16 | g << 8 | b  # rgb in [0, 255] int range
    color_value += 1
    r = color_value >> 16
    g = (color_value ^ (r << 16)) >> 8
    b = color_value ^ (r << 16) ^ (g << 8)
    
  • repeat for the next, adjacent vertex selection, and so on.

Using the solidify modifier in Blender might help, although it would double the vertex count.

The vertex colors should determine this.

There might be an easier way; if the export model file format supports it, then one could simply model a different object for each major part of the ship and give it a name that starts with a padded index number (e.g. 000, 001, 002, etc.), relying on the fact that these objects will be exported as different GeomNodes with those names. (This would not work with .obj files for example, but since these cannot even store vertex colors – correct me if I’m wrong – they’re not suitable anyway.)

I do think that this is a good idea, indeed! :slight_smile:

Ah, the way that I colourised them results in neighbouring triangles often having slightly different colour-values–thus resulting in a smooth flow of triangles appearing, as in the shader-version.

It likely wouldn’t be too hard to modify the Blender-script to quantise the measured distances a bit, thus resulting in multiple triangles with a single colour.

I have no argument! It depends on the exact effect that you want–either plates or triangles could work, I think. And if plates are what you have in mind, then plates can likely be done!

That said, I do see one major advantage to plates, one that addresses a worry that I’ve had:

Creating a model with enough triangles to make the “per-triangle” approach work nicely results in a very high-poly model.

Conversely, if sections of the models are to appear as “plates”, then the model can have a more game-appropriate polygon-count and polygon-distribution, I daresay.

Honestly, with a model of reasonable poly-count, this can likely enough just be done by hand.

After all, there are likely to be rather fewer “plates” than triangles, and far fewer than the number of triangles likely to be used in a “per-triangle” approach.

That would likely work, I daresay. However, I also think that it would likely be incompatible with the “random generation” idea. After all, under that idea, the order in which parts are added, and the parts being used, is likely to vary from generation to generation.

I remembered that there was a good experience similar to yours, perhaps you can borrow the idea and implementation of something.

It looks like the zip file has ended its existence, it remains to hope that someone has a copy.

2 Likes

Good point. In that case, the part objects could instead be named after what they represent, e.g. “landing_gear”, “wing_left”, “wing_right”, etc. and the creation script could keep these parts as separate nodes in a hierarchy then. In the demo, each different layout could be defined as a simple list of those names in the desired order, with such a list chosen randomly. Seems like something that should work.

That could work, indeed.

What I had in mind was something a little more dynamic:

The source folder would hold multiple models for each part-type–so three cockpits, four wings, five guns, and so on–each named for its part-type followed by a number. The program would load all of these in and store them in lists.

Then, when told to construct a ship, the system would start with a central piece–let’s say the cockpit. From there it would move outward, using empty-nodes provided in the piece-models as “hard-points” to which new sections can be attached.

The algorithm, would have rules in place to limit the final size of the ship, and to ensure that all required pieces are included.

One thing that might be worth noting is that, as I envisage it, this system would only generate parts to one side (to the left, for example). Then, when the mesh is generated, it would duplicate the polygons from the parts to the other side. This would enforce symmetry, and furthermore reduce both the number of parts to be handled by the construction process and the number of polygons to be considered by the mesh-generation step.

I was toying with a similar idea myself; I think adding a PDA, or some kind of console in an office to the beginning of the game would be nice and ergodic. I suppose I lean towards a small computer of some kind which the player character takes with them from the beginning. Maybe it could “link in” to the hangar environment (for choosing spaceship parts for instance) and then “hack in” to some part of the space station environment, to locate the space suit for instance.

Thanks! We’ll see how far we can take it aesthetically, and with different ship parts.

All this seems achievable enough; we are thinking about this harder than many game designers might, trying to make the generation realistic enough to be compelling. Plenty of games just have stuff “pop in”, which I realize would not satisfy us lot. :slight_smile:

The higher the number of triangles, the slower the model comes out, which makes me think that we should make a ship “frame” and then add smaller bits to that structure with the hangar robots. This would be compatible with the “slow” generation code and the idea of interchangeable ship parts.

However, I like the idea more of generalizing “plates”, which would come with the fewest disadvantages I imagine, at some time cost to figure out how to do it cleanly.

Hmm, starships are usually very symmetrical. This may be a good point for optimization. One thing I’d like to achieve is to not “cheat” on the ship simulation – I would like the ship to be generated fully modeled inside and out, with no loading screens as you enter it or exit it.

They are triangles being generated, but not in a compelling order. The rocket engines on the ship are separate model parts, closer geometrically to being cylinders, and they generate in a nice spinwise order for the most part. I haven’t spent time yet on figuring out how the vertex colors work, but as you point out this is probably the way to fix it.

This could make for a nice common interface and connecting thread for the various gameplay segments, perhaps.

In all fairness, that’s at least in part because the reason that we have this is in order to show off procedural generation. Having the ship just pop in wouldn’t exactly achieve that. :stuck_out_tongue_winking_eye:

If I’m understanding Epihaius correctly, I don’t think, at least, that we’re discussing composing all parts each of identical components.

Rather, my understanding is that the idea is simply to model the ship-parts in sections, and to have those sections appear all at once.

Doing it that way shouldn’t call for too much difficulty in the modelling process, I think.

Hmm… That should be doable. It incurs a bunch of geometry that’s rather wasted once we reach the actual ship-flight portion of the experience, but with some canny modelling it might not be too bad.

Ah, I didn’t mean to convey that. I meant that we might find a solution to piecemeal the triangle mesh out as groups of N triangles, so that the generator could scale somewhat arbitrarily to whichever designs we want.

Ah, I see. Yes, that does make more sense, I think!

Still, I wonder whether designating sections by hand might not be more effective: a script-based approach might produce results that are a bit overly uniform, or at least lack apparent intentionality and thus a sense of being “built”.

So, I believe I have found the cause of the geometry being rendered out of order. This seems to occur when part of a mesh is subdivided in Blender, only to those “parts”. I’m not familiar enough with the generator code to determine if this is an easy fix, or if it’s just preferable not to do partial subdivision during the modeling phase. Either way I think I can make it work on the modeling side.

1 Like

Nice idea :slight_smile: !
Lol, throwback to the old days: “Welcome back, commander Koenig!” :grin:

Agreed. As long as there won’t be any textures containing text, otherwise it would get mirrored.

Just to be clear, there are two different types of model pieces that we’re dealing with:

  1. the large, visually distinctive components, such as:
    • landing gear
    • main body
    • cockpit
    • left and right wings
    • rocket engines
  2. the smaller chunks that the above components are made up of: the hull plates.

Concerning the hull plates, their size can be chosen in function of the overall size of the starship and is independent from the actual number of triangles. This means that the time it takes to completely generate the starship would depend solely on the number of plates, regardless of triangle-count.
So it should be doable to speed up construction time as desired by enlarging the average plate size (relative to ship size) – and thereby decreasing their number. To make the whole generation process look more interesting, it might be worth varying the plate sizes a bit – as well as their shapes, in fact. Do they have to be perfect rectangles?

Some thoughts about the large components:

  • the order in which they are built is something we should decide upon and mostly depends on what we feel makes the most sense. For example, if the landing gear is generated first, can the main body realistically be built on top of it (i.e. is there sufficient support)? Or should we perhaps start with a kind of scaffolding or skeleton (the “frame” mentioned by Simulan made me think of this – it could be a very simple wire-frame-like construct consisting of a handful of strong metal bars, moulded into the general shape of the starship, for instance). There could also be a small number of supporting “anti-gravity beams” to hold things in place. Some level of “suspension of disbelief” will be expected of the user, of course, so it doesn’t have to be hyper-realistic either (it’s sci-fi, after all :wink: );

  • in Thaumaturge’s shader example, they are generated in strict succession, one component generated only after the previous one is completely finished. Should it stay this way? As long as the geometry area of the “parent” component where they’re supposed to be attached to has materialized, their contruction could already start I think – this would further speed up the total generation time.

The generator code merely creates an exact copy of the original GeomTriangles primitive, over time. It needs the triangles in that original primitive to already be in the correct order, which is the job of the creation script. The latter does this by sorting them by ascending color values. If your models have a uniform vertex color, then that script can’t do anything useful; what likely happens in that case is that the triangles are (more or less) kept in the order that the modeling program itself added them. When you do something with the models after their creation, such as subdividing them, then that initial order is disrupted, which is probably what we’re seeing.
Apart from ensuring that the desired triangle order (or rather plate order) is maintained (despite further operations on the geometry), vertex colors can also identify the triangles belonging to each hull plate.
Hence the need for unique vertex colors in ascending order, relative to the boundaries/seams of the large components.

So, I am eagerly awaiting correctly colored models to further experiment on :wink: .

1 Like