[Solved] Loading 1000 instances of a tree

Is it possible to use 1000 instances of the same object?

I have managed the code to drop a series of trees down to the correct height above a terrain using a collisionRay to find the appropriate value for setZ().

Now… Id like to place a thousand random trees and I really dont want to load the same object 1000 times. Any Hints?

Sure.

If you truly want instancing, you just call tree.instanceTo(newParent) 1000 times. Each call will return a new instance, attached to the indicated parent node. Note that each instance must have a different parent, and you will want to apply a unique transform to each parent, so that the trees are not all in the same place.

But usually, instancing is more trouble than it’s worth. You might find it much easier to work with duplication. In this case, just call tree.copyTo(newParent) 1000 times. Each call will return a new copy. All the copies can be attached to the same parent, if you like, and you can set a unique transform directly on each copy.

It’s not really wasteful of memory to use copies instead of instances, since the actual vertex tables are still shared. And rendering time is the same either way: in either case, the graphics card still has to draw 1000 trees.

Note that if you are building up geometry by assembling pieces like this, you should also consider with calling nodePath.flattenStrong() when you are finished. Proper use of flattenStrong() is a complex optimization problem.

flattenStrong() will combined adjacent nodes together, accumulating many smaller polysets into fewer larger polysets, which can be an important performance optimization when you are going to see all of those polysets at the same time anyway. (It also, incidentally, duplicates out instances.) Of course, it also impedes culling–if all the trees are combined into one object, then all the trees must be drawn all the time, which can hurt performance. So the right way to use flattenStrong() is to collect together a number of trees that are all nearby each other under a common node, and call flattenStrong() on that.

Of course, whether you use flattening or not, you’d still want to group together nearby trees under a common node anyway, to maximize the benefit of hierarchical scene graph culling–if the entire volume of collected trees is outside the viewing frustum, then Panda won’t need to consider each tree individually.

Or, if your CPU and graphics card are good enough, you can just attach all of the trees to one node and not think about performance optimization–you might be happy enough with the resulting frame rate. This is one of the strengths of Panda: you can put off the optimization issues if you don’t want to think about them. :slight_smile:

David

wow.
thanks for the quick and thourough reply!

Im loading a world up with trees and will having an actor(s) moving around through the forest, so im not sure how much optimzation I can do if I still expect to see what are currently far trees – close up later.

I will look into copying the nodes… sounds like a winner. I just knew there was a better way than loading 1000 models.

im sorry.

Im going to need to see an example. There are no examples of "copyTo() that I can find in the manual or forums.

I have:

        self.tree4 = loader.loadModel(MYDIR+"/models/palm1")
        self.tree4.reparentTo(render)        
        self.tree4.setHpr(Vec3(0,0,0))
        self.tree4.setPos(Vec3(10,10,100))
        self.tree4.setScale(1)
        
        self.tree4.copyTo(self.tree5)
        
        self.tree5.reparentTo(render)        
        self.tree5.setHpr(Vec3(0,0,0))
        self.tree5.setPos(Vec3(11,11,100))
        self.tree5.setScale(1)  

and I get:

self.tree4.copyTo(self.tree5)

AttributeError: World instance has no attribute ‘tree5’

(but i’m sure you already knew this?)

There are many gaps in my knowlege so far.

Instead of:

self.tree4.copyTo(self.tree5)
self.tree5.reparentTo(render)

Do:

self.tree5 = self.tree4.copyTo(render)

There are three functions that are variants on the same thing: reparentTo, copyTo, and instanceTo. Each of these has a similar interface: you specify the new parent of the node. In reparentTo, it moves the node, and doesn’t return anything. In both copyTo and instanceTo, it creates a new node (or a new instance), and returns the corresonding new NodePath.

David

ok, on a roll…

However, I think Im getting stuck on the relationship between objects and array elements.

Here, I am creating the tree objects…


        trees = []

    
        for i in range(10):

            x = random.randint(1,100)
            y = random.randint(1,100)
            s = random.uniform(1,2)
            
            # self.tree = NodePath(PandaNode("tree"))           
            self.tree = self.tree4.copyTo(render)
               
            #self.tree.reparentTo(render)        
            self.tree.setHpr(Vec3(0,0,0))
            self.tree.setPos(x,y,100)
            self.tree.setScale(1,1,s)
            
            trees.append(self.tree)

next, I am setting up the collisions…

       for i in range(10):
            
            self.treeGroundRay = CollisionRay()
            self.treeGroundRay.setOrigin(trees[i].getX(),trees[i].getY(),trees[i].getZ())
            self.treeGroundRay.setDirection(0,0,-1)
            self.treeGroundCol = CollisionNode('treeRay')
            self.treeGroundCol.addSolid(self.treeGroundRay)
            self.treeGroundCol.setFromCollideMask(BitMask32.bit(1))
            self.treeGroundCol.setIntoCollideMask(BitMask32.allOff())
            self.treeGroundColNp = trees[i].attachNewNode(self.treeGroundCol)
            self.treeGroundHandler = CollisionHandlerQueue()
            self.cTrav.addCollider(self.treeGroundColNp, self.treeGroundHandler)

and running this BEFORE the move() loop to try and plant the trees

        self.cTrav.traverse(render)
      
        ####
        ####    New - check for Tree - ground Collisions
        ####
        ####    Attempting to plant trees
        ####

        for i in range(10):
        
            entries = []
            for j in range(self.treeGroundHandler.getNumEntries()):
                entry = self.treeGroundHandler.getEntry(j)
                entries.append(entry)

                newX = entries[0].getSurfacePoint(render).getX()
                newY = entries[0].getSurfacePoint(render).getY()      
                newZ = entries[0].getSurfacePoint(render).getZ()
                trees[i].setPos(newX,newY,newZ)
                print entries

The trees seem to be still stuck in the air. I can see the collision nodes scattering the ground, so I know its at least trying. Its also gotten very slow. I dont think I’m dont somthing efficiently.

I really do appreciate your help. I’ve been poring over the documentation and forums for hourse and I believe I have made a lot progress, but Im probably doing something very stupid.

Im not sure I did it the right way or not…but

I did get the program to function…

I have 4 seperate arrays that breakdown the various parts of the collision system…

    trees = []
    treeCol = []
    treeColNp = []
    treeHandler = []

I now setup my “tree planter” like this…

      for i in range(TREECT):
            
            self.treeGroundRay = CollisionRay()
            self.treeGroundRay.setOrigin(trees[i].getX(),trees[i].getY(),trees[i].getZ())
            self.treeGroundRay.setDirection(0,0,-1)

            treeRay = 'treeRay' + str(i)
            
            self.treeGroundCol = CollisionNode(treeRay)
            self.treeGroundCol.addSolid(self.treeGroundRay)
            self.treeGroundCol.setFromCollideMask(BitMask32.bit(1))
            self.treeGroundCol.setIntoCollideMask(BitMask32.allOff())
            self.treeGroundColNp = trees[i].attachNewNode(self.treeGroundCol)

            treeCol.append(self.treeGroundCol)
            treeColNp.append(self.treeGroundColNp)
            

            self.treeGroundHandler = CollisionHandlerQueue()
            treeHandler.append(self.treeGroundHandler)
            
            self.cTrav2.addCollider(treeColNp[i], treeHandler[i])

I dont think its pretty, but it seems to work. Notice that I am using a second traverser. when I did it originally and everything was using one traverser, it bogged down VERY SLOW. By splitting it into two, one at the onset when i do the one-off of placing the trees and a different one later in the move() to track normal collisions, it seems to have sped it up a lot. Something nags at me though that this is not the most efficient way to code this?

Also, I used collisions to plant the trees, but they don’t have active collisions between the actor and the trees?

This sounds like the right solution. When you have everything in one traverser, then it means it is re-computing all of the tree positions every frame, so of course it is much slower. By putting your one-shot tree calculations in a traverser that you only run once, instead of in the global traverser that gets run every frame, you avoid this extra cost.

If you want your actor to avoid walking through the trees, you need to create a collision solid around each tree for this purpose. You can do this once, at the time you create the first tree and before you copy it to each of the duplicate trees. I suggest a CollisionTube object; see the manual page on creating collision solids. Note that you don’t have to (and shouldn’t) add this CollisionTube to any traverser, since your trees won’t be actively detecting collisions with other objects.

David

ok, so only the action character needs to be in the traverser in the loop? everything else is static? Does this apply to the ground as well?

OK, I tried both adding a collision BEFORE instancing the trees and AFTER.

My current version looks like this:

           ###
            ### This second Collision setup is so you run into the trees
            ###

            tube = CollisionTube(10, 10, 100, 10, 10, 110, 1)
            self.treeCol = CollisionNode('tree'+str(i))
            
            self.treeCol.addSolid(tube)
            self.treeCol.setFromCollideMask(BitMask32.alloff())
            self.treeCol.setIntoCollideMask(BitMask32.bit(1))
            self.treeColNp = trees[i].attachNewNode(cNode)
            
            treeCol2.append(self.treeCol)
            treeCol2Np.append(self.treeColNp)
            
            self.treeColHandler = CollisionHandlerQueue()
            treeColHandler.append(self.treeColHandler)

and of course, this is after instancing.

Either way, I get no collisions that I can detect. Isnt the point of the traverser to detect collisions? If the objects are not added to the traverser, I dont see how they are going to get detected?

I would much prefer your original suggestion and just tie the collision to original tree before creating the array. I just dont know how to code it.

This line seems strange. Why are you attaching the node cNode to the tree, when the node you have just created the collision tube in is called self.treeCol? I don’t see anywhere in the code where you actually attach self.treeCol to the graph.

The traverser is so named because it traverses through the scene graph. It detects the collision solids that you attach to the scene graph. Of course, at least one collision solid (the one around your avatar) must be explicitly added to the traverser. This is the “from” object. All of the other objects–the one around your trees–are “into” objects, and should not be explicitly added. Re-read the section in the manual about collisions; it describes this in a bit more detail.

Just do what you are doing above, but instead of attaching the collision node to self.trees[i], attach it to self.tree4. It will then get duplicated along with the tree’s geometry when you call self.tree4.copyTo().

David

I tried that first.

I got nothing. Is my collisionMask correct? I know almost nothing about Collision Masks.

I switched to doing it long hand when the self.tree4.copyTo() didnt seem to do anything.

Actually, that cNode might be my whole problem . that section was cut and pasted from another example.

After my hard drive finishes recovering, I’ll check that.

Thank You Dave… just the ticket!!!

200 collidable trees and 45 FPS! Im happy. WooHoo!

Really old but do you have the current working code to post?