Why is maze rendering so slowly?

I am new to Panda3d. For a first test app I am building a simple 3D maze generator.

Originally, I was using the “visual” module, until I figured out that it sucks. The maze was running unbelievably slow… taking several seconds to render a single frame every time the “camera” moved. So, I switched to Panda3d… since Panda is a much higher-level tool, I thought it would render my maze algorithm more efficiently.

But, although Panda renders it significantly faster than visual, it is still quite slow. It takes up to ten seconds after launching the app before it will render the whole maze. I’m not sure if my code is inefficient, or if I’m not doing something I should be with Panda… Seeing all the amazing things Panda can do, though, I highly doubt it should be running this slow. Its got to be a problem in my code.

Note-- I did have to increase the recursion limit to 10,000 instead of the default, but that shouldn’t make it run as slow as it is, right?

Source at:
pastebin.com/LS2cCdSi

Hoping that someone can point me in the right direction here.

I only skimmed your code, so my apologies if I’m missing something.

However, based on that skim I wonder whether the problem might not be that you’re creating too many geoms (not too many polygons, please note, but rather too many separate geometry objects): as I understand it, modern graphics cards tend to be very good at pushing through a great number of polygons, but not good at handling a great number of objects.

You might want to consider using one of the “flatten” methods; depending on the size and complexity of your maze, it might be a good idea to not flatten the maze into a single geom, but rather a small number of geoms (to allow for things like culling to still take place).

A little more information on flattening should be available here.

Thanks so much for the response. I just read through the information on ‘flattening’…

I don’t know if it applies here, but I just read through a section in the panda manual on creating instances when you have multiple copies of the same object. It would seem to me that since everything is an instance of my ‘Cuboid’ class that instead of adding 151515*6 walls to “render” directly, I should instead add that number of instances of the same object to “render”.

Is it valid to structure the code this way? Would it make the rendering more efficient?

Hmm… I’m honestly not sure–hopefully someone better-versed in such things might be able to help. I think that GPU-based geometry instancing would help, but that the sort of instancing mentioned in the manual (I presume that you’re referring to this page) only really helps with animations, not geom count.

I’ve just noticed something else in your code: why does Cuboid inherit from DirectObject? To the best of my knowledge, DirectObject is intended for objects that should receive events and run tasks–world objects, for example–not for scenery objects, as appears to be the case here. Might NodePath not be a better base class?

[edit]
I just did the maths on the numbers that you posted–if I’m not much mistaken, you have twenty. Thousand. Walls. That is… and awful lot. No wonder the poor thing is executing at a crawl. :confused:

If you want that many walls, I really do recommend first splitting your scene graph up a bit, dividing up your walls into a quadtree or octtree as called for, then flattening the walls in each leaf of the tree.

Thanks again. I’m still relatively new to working with the scene graph structure. I’ll definitely try to restructure the geometry.

Have been trying to work in the “flatten” feature… they are all methods of NodePath, correct?
I changed my Cuboid class to inherit from NodePath instead of DirectObject (that already made it render substantially faster.) Since that is only object that inherits from NodePath, I added a call to self.flattenStong(). For lack of anywhere else I can see to call flattenStrong() from, and since the Cuboid class has ONLY a constructor, I call it at the very end of the constructor. Of course, then I get this error –

DirectStart: Starting the game.
Known pipe types:
glxGraphicsPipe
(all display modules loaded.)
Traceback (most recent call last):
File “/home/nino/python/vpython/3dMaze/main.py”, line 359, in
board = Maze()
File “/home/nino/python/vpython/3dMaze/main.py”, line 183, in init
self.render()
File “/home/nino/python/vpython/3dMaze/main.py”, line 316, in render
cell = Cell(cx, cy, cz, N, E, S, W, F, B)
File “/home/nino/python/vpython/3dMaze/main.py”, line 164, in init
rn.flattenStrong()
AttributeError: C++ object is not yet constructed, or already destructed.
[Finished in 2.2s with exit code 1]

I can definitely see why I’m getting this error, but I am unsure about where to put the fall to the flatten function…

First of all, I don’t recommend calling “flatten” on the individual Cuboids–I’d suggest collecting them into groups of adjacent Cuboids, parented below a common NodePaths; I don’t think that you’ll likely see much improvement otherwise. If you’re not using an octtree or quadtree, you might still group your Cuboids beneath a 2D (or 3D, if called for) grid of NodePaths, which you then flatten. This should help both in terms of the number of geoms (producing a single geom for all of the contained Cuboids), but also in terms of culling (by allowing Panda’s culling system to remove more unseen geometry in each step).

Either way, the answer to your immediate problem is, I believe, to call flatten on the object produced after it has been created.

If you’re sticking with just the Cuboids (that is, not using any of quadtree, octtree or the grid that I mentioned above), you might have something like this:

cuboid = Cuboid(<parameters here...>)
cuboid.flattenStrong()

If you’re using a grid, then, once the Cuboids have been created and parented below the grid nodes, call the “flatten” method on each grid node.

The error means that you’re inheriting from NodePath, but you’re not calling the parent class constructor (NodePath.init(self)) in the constructor. However, you should ask yourself what you’re gaining by inheriting from NodePath; it is not a magic formula to solve any problems here, and from what I read, it doesn’t solve any problems at all in this case.

It is very likely, as Thaumaturge suggested, that your problem is that you have too many Geoms. In your code, you create a new GeomVertexData (and therefore a new Geom) for every wall segment. This is very performance inefficient. Flattening helps slightly (though flattening individual cuboids as suggested is pointless), but it would be far more wise in this case to simply generate all the geometry as part of a single, large Geom in the first place or perhaps a few Geoms (one for each major section of the maze), rather than one Geom per piece of wall.

Instead, I would create a single MazeRenderer class which stores a set of GeomVertexWriters, and visits all the cells and generates the polygons for those segments using the same GeomVertexWriter object.

Note that you can significantly speed up performance of geometry generation by telling the GeomVertexData and GeomTriangles beforehand how many rows you’re going to write - consult the API reference for this.