Flat infinite grid textured floor


I’m new to Panda3D and I would like to build a “simple” flat infinite grid floor as simple and time-saving as possible. I already tried some things and created a fixed size grid but since my camera is constantly moving forward with undefined duration, it is a problem to create an infinite number of cards/tiles. Apparently, everything with more than 300 tiles is not possible with the code below (stolen from some topic here on panda3d.org). I also found the infinite terrain article referring to the setInstanceCount(n) method. This sounds quite complicated to me as a beginner though, since I only need a simple and flat grid texture. Does it make sense to use it for my purpose or are there any better/easier solutions? I also looked into the infinite-tunnel demo. Could this be a good solution for my problem? I’m not sure what’s the best and most efficient solution.

Thank you for any help! :slight_smile:

def initGrid(self, dimX, dimY):

        self.levelroot = render.attachNewNode("Level Root")
        self.floorname = "models/floor.egg"
        self.levelfloor = [[0 for col in range(dimY)] for row in range(dimX)]
        for x in range(dimX):
            for y in range(dimY):
                self.levelfloor[x][y] = loader.loadModel(self.floorname)

Hi, welcome!

You can just create a very large plane and scale the texture so that it repeats across the plane.

Hi rdb! Thanks a lot for your quick reply.

But how do i scale the texture so that it repeats? I already read the article about wrap modes but it did not really work out the way i wanted. My egg file already references the texture but if i apply the texture with setTexture in python, it does not replace it. It still shows the referenced texture by the egg file.

Could I create a card without any egg file, set a huge scale and apply a texture which then repeats across the card?

I’m sorry, if I’m asking stupid questions…

You could use the CardMaker class to do this, I believe. A simple approach might look something like this:

# In your "import" statements:
from panda3d.core import CardMaker

# In your code:
myCardMaker = CardMaker("my card maker")

card = render.attachNewNode(CardMaker.generate())
card.setP(-90) # This rotates the card to face upwards
card.setScale( <whatever scale you want> )
card.setTexture( <your texture> )

There is a caveat in that you might want to load your texture with mip-mapping enabled.

However, if you already have an egg-file that defines a textured model, then you might still be able to use that. (Presuming that you can’t just edit the model in a 3D-modelling program, of course.) Scaling a texture that’s already present on the model doesn’t require re-adding it. Instead, you simply set a texture-scale, something like this:

myModel = loader.loadModel( <your model-file> )
myModel.setTexScale(TextureStage.getDefault(), <your desired u-scale>, <your desired v-scale>)

If you want the grid to appear to be infinite, then you might perhaps do something like this:

  • Create your card, whether loaded from file or made by CardMaker, or otherwise.
  • Make sure that it stays centred on the camera. Depending on your intended functionality, this may be as simple as attaching it to the camera or as complex as calculating a position based on the camera’s location and then applying that.
  • And finally, using a task, update the card’s texture-offset (using NodePath’s “setTexOffset” method) such that the texture is moved counter to the camera’s movement.
    • (If you allow the camera to rotate, you may also want to update the card’s texture-rotation (via NodePath’s “setTexRotate” method).)

FWIW, you can try calling set_texture with a priority, such as:
set_texture(my_texture, 1)
This will override (but not remove/replace) the texture attribute of a lower-level NodePath (or Geom), so the new texture will show up on the model geometry.

As a new user you will probably benefit from calling model.ls() and checking the output in the console, as this can be very helpful in figuring out node hierarchies and what render states are applied where.
For instance, if you load the smiley model:

        model = self.loader.load_model("smiley")

you will see the following output:

ModelRoot smiley.egg
  GeomNode  (1 geoms: S:(TextureAttrib))

This shows that the texture is not set on model (the ModelRoot) and not even on the first child of the ModelRoot (which is an unnamed GeomNode in this case), but on the first Geom within that GeomNode.

Now if you do this:

        model = self.loader.load_model("smiley")
        my_texture = self.loader.load_texture("maps/envir-reeds.png")

the output becomes this:

ModelRoot smiley.egg S:(TextureAttrib)
  GeomNode  (1 geoms: S:(TextureAttrib))

While if you do this:

        model = self.loader.load_model("smiley")
        my_texture = self.loader.load_texture("maps/envir-reeds.png")

the output becomes:

ModelRoot smiley.egg
  GeomNode  (1 geoms: S:(TextureAttrib)) S:(TextureAttrib)

So you see that the texture attribute set for the Geom remains and ignores any texture set on a higher level, except those set with a higher priority.
The only way to truly get rid of that texture attribute would be to set a new RenderState for that Geom, e.g. an empty one:

        model = self.loader.load_model("smiley")
        model.get_child(0).node().set_geom_state(0, RenderState.make_empty())

Hopefully this info will save you some hair/headaches in the future :slight_smile: .

1 Like

Hello Thaumaturge and Epihaius,

thanks for your replies and hints! Today I tried to work on my problem again.I made it work with the card+texOffset approach. The card grid is moving with the object, the camera is looking at. The object’s coordinates are passed as a and b into the second method, divided by the grid scale. Not the best performing method i guess but it works fine for a grid of 60x10 cards which is enough :slight_smile:

    def initGrid(self, dimX, dimY, scale):
    cm = CardMaker("cardmaker")
    tex = loader.loadTexture('floor.png')

    self.levelroot = render.attachNewNode("Level Root")
    self.levelfloor = [[0 for col in range(dimY)] for row in range(dimX)]
    for x in range(dimX):
        for y in range(dimY):
            self.levelfloor[x][y] = render.attachNewNode(cm.generate())

def offsetGrid(self, dimX, dimY, a, b):
    for x in range(dimX):
        for y in range(dimY):