Dynamic Instancing?

I’ve asked a whole bunch of questions and so far everybody’s been quite helpful. I appreciate that.

Here’s another question:

I’ve looked into using instanced to dynamically insert objects into the Scene Graph… Instancing is great… if I want to insert the same object.

The problem I’m trying to solve is this: I have a ‘tile’ map that has… let’s say 20 possible versions. I create a 2D map with … let’s give the dimensions of 10x10 ‘tiles’ for the ‘tile map’.

Naturally, this map won’t have identicle tiles, so the ‘loader’ would need to read off each element of an array, see what ‘tile’ is in place array[i, j] and then add that tile to the scene graph.

Using the instancing described in the manual, the best way I can think of to do this is to run through several passes, each pass setting up only one specific type of object. This is fine for load-time, but would suck if I needed to change it or re-load it during runtime.

Any suggestions? What would be a good way to approach this?

Just keep a cache of all the models you might need.

So let’s say, hypothetically, that you’ve got 20 different tiles. In that case, you create a global variable containing them:

TILES=[]
for i in range(0,20):
TILES.append(loader.loadModel(“tile” + str(i)))

Then, you can just insert TILES[1] into the scene whenever you need tile 1.

  • Josh

I think that’s what I needed… It looks like I could use this code later on when I add functionality to scan a directory for ‘floor textures’ during load time. I’ll give this a shot and see what I can come up with.

Thanks!

Hmm, I’m not sure I understand the problem. You can certainly build up your scene graph at runtime; in fact, that’s one of the whole points of having a scene graph, is to make this sort of operation easy.

You don’t need to use instancing to build up a scene graph, and in fact, the occasions in which you actually need to use instancing are really very rare. Usually, you will just copy nodes if you need to have a node appear more than once, since you can then modify each copy as you desire. But in the case of your tile map, perhaps your tiles all came from different places anyway.

David

Maybe a better explanation of what I’m trying to do would help:

Essentially, I want to create a 3D tile-based game. I’m not familiar with Python so I’m writting a few experiment programs and one of them is the TileMap ‘engine’.

Before working with 3D stuff, I worked with 2D… actually, that was a long time ago but the concepts I used in the 2D games seem to be able to be adapted to 3D… I just don’t work with the up/down component.

Essentially what I’m trying to accomplish is an effective method or reading in a list of values. I can do the reading part and I can do the array part (I’ve figured that out finally with Python… just requires some extra work).

The question now is that I want to, while setting up the scene, run through the array, read the value of each element and then ‘place a tile’ based on that value (e.g., value 1 = marble floor, value 2 = grass).

I have a 3D model that represents a tile… using Panda3D’s dimensions of 1x1… simple enough. But, loading in a new copy of every single tile seems redundant and extremely inefficient… so I use the loadModelCopy function so I only load the model once.

The problem now is that I need to reparentTo each ‘instance’ of this model. But what if I want a new texture at any given point? setTexture() doesn’t seem to work as well as I’d hoped.

So thats’ the first part: laying down tiles.

After that, I wanted to lay down building geometry, e.g. walls. So let’s say that I have eight styles of wall: North, South, East, West, North East, North West … and so on… each wall for a particular edge of a ‘tile’ and for corners.

I think I may have just figured it out as I’m describing it here… but I’d still like some input to see what others come up with.

===============================================

As a seperate note: I can’t seem to get setTexture() to work… I’ve used the examples in the manual but it’s just not happening. I’ve also noticed in the console window that it says "unable to load texture at path … ". When I add an extension to the path name, I don’t get the error message but the texture still doesn’t change. Is there a bug/quirk in Panda3D that I’m not aware of or am I just doing something wrong? My code looks like this:

model = loader.loadModelCopy("someModel")
tex = loader.loadTexture("someTexture")     # also using a '.jpg' extension

model.setTexture(tex)

model.setPos(0, 0, 0)
model.reparentTo(render)

After this, the model continues to use the texture set in its internal texture definition. Should I remove the texture from the model before loading it into Panda3D or is the texture supposed to be in the same directory as the model itself? (I have the texture in a totally different directory than the model).

Thanks again for the help!

This is a good set of questions. It’s good to see you tackling a meaty problem like “dynamically building a world map based on a datafile;” once it’s working, you should have a pretty good understanding of Panda’s render tree and texture mapping techniques.

I think you’re hitting a couple of issues with your current demo:

  • unlike the model loader (which can auto-detect “bam” and “egg”), the texture loader doesn’t assume a suffix for the file. So to load “someTexture,” you’ll have to specify as “someTexture.jpg”.

  • While Panda’s render tree does allow you to specify a texture higher up the tree to apply to all the geometry lower down the tree, it gives priority to already-existant textures.

When you load a model from an egg file and reparent it to render, you get a tree that looks like this:

<render>
|-<mymodel.egg>
  |-model geometry (possibly unnamed) :T <-- texture is applied here

Applying the texture to the loaded model actually applies it to the egg container NodePath, which means you get a result like this:

<render>
|-<mymodel.egg>:T <-- the texture you applied
  |-model geometry (possibly unnamed) :T <-- oops! overrides the above

The solution I generally use is to specify a higher priority (the second argument to setTexture) when I replace textures on a loaded model. This normally defaults to 0 (to 1? anyone remember?), but if specified it will affix a priority to the texture at that node. When the tree is rendered, textures with higher priority numbers (i.e. closer to infinity) will clobber lower-priority textures, even if they are lower in the tree.

So using


model.setTexture(tex,3)

Should do the following:

<render>
|-<mymodel.egg>:T(3) <-- the texture you applied (prio. 3)
  |-model geometry (possibly unnamed) :T <-- clobbered by above prio. 3

The original texture will be ignored, and you should see the one you applied.

Best of luck,
Mark

Mark’s advice is spot-on. The default priority is 0, by the way.

As to this:

This is an excellent strategy; loadModelCopy() is very efficient, and does indeed hit the disk only once. However, you should understand that it returns a completely unique copy of the model each time you call it, not an instance of the model. This means you are free to make whatever changes to the model you like, without affecting the other copies. And the copy is very efficient, so there’s no need to worry about that.

For the record, there is also the method loadModelOnce(), which does indeed return a new instance of the model each time. This is more dangerous than loadModelCopy(), because it’s easy to accidentally change the attributes of all of the other instances; and for this reason I don’t recommend its use unless you are fully comfortable with all of the implications of instancing.

David

Thanks guys for your help. I’ve now got what I wanted working perfectly. It’s somewhat… slow… when everything is visible but I get the feeling that’s from the number of potential objects in the scene graph. I havn’t exactly optimized it yet… everything is stuck onto the root of the scene graph (render).

Anyway, thanks for the help. I’m well on my way in the direction I want to be headed in. Thanks again!