Performance impact on thousands of Nodes

Hold down the right mouse button and pull it away from you. Use the pinched wheel for viewing.

不客气 :slight_smile: . It is because of people like you who appreciate the help they receive that I am motivated to keep helping out :wink: .

What I’ve read about Blender 2.8 (I don’t really use Blender myself) is that it’s now possible to select multiple objects in object mode and then go to edit mode and unwrap all of them at once.

But since the textures are just squares (one for each block side), I think it should actually be easy to use a paint program like Gimp to create the atlas manually. You could copy and paste all of the needed square textures side-by-side into one texture (the atlas) big enough for all of them (something like 1024 x 1024 would be good). Fill the width and height of the atlas with an integer number of block side textures, so that e.g. 8 of them take up the entire width or height; this makes it easier to calculate the UV coordinates needed for the model vertices.

To know where (and in what orientation) to place each side texture, you can implement an algorithm that offsets each side of a block (and provides an additional offset for the different types of blocks as well) and let it print out the UVs it computes. My code sample already does something like that (at least for the sides, whose textures are expected to be placed left-to-right like this: left, back, bottom, right, front and top, where bottom and top might need to be rotated to suit your preference), but you’ll need to adapt it to take the number of different block types into account.

This will probably be the most time-consuming part of your initial development, but I’d still do it manually rather than using Blender and then loading and analyzing the exported model, but that’s your choice.

If your animation frames only depict the cracks themselves, then you can definitely use multitexturing to blend them with the main block texture. For this purpose, the background should be white; then you can use the default modulate blend mode to make the main block texture show through the animation frames.

To play the animation, it will probably suffice to call set_texture (using the TextureStage associated with the “top texture”) on the block nodes 4 times, using a different animation frame each time. The alternative would be to use a 3-D texture, but I’m not sure it’s worth creating 3-D UV coordinates for such a short animation.

What I would like to know, is if the animation is used to show a gradual decay of the block (i.e. each time the player hits the block, the animation advances one frame), or if it plays from start to finish without delay and is immediately followed by the block falling down?
More specifically, can it happen that multiple blocks need to be animated at the same time (perhaps even with each block showing a different frame)? That would make things a bit more complicated, requiring the entire chunk model to use multitexturing and adjusting the UVs for specific vertices.

If only one block animates at any given time, you could just separate that block first, set up multitexturing for it and then play the animation on it.

Yes, that is correct.

It is indeed only creating a new cube node; it is merely a helper method, whose cube creation code was split off from remove_cube, to prevent the code from becoming too cluttered.

Even though I couldn’t get it to work(only worked once but I’ll try to figure it out), it seems like a really good idea of yours not to bother to separate the mesh. The question is will the texture on the side that was hidden still show? Thank you.

#1 If I understood correctly, I need to draw in 2d, put it all the textures in a big square? Then make an algorithm that knows where the appropriate textures are for every side of each block? If I could, instead an algorithm, just store all the coordinates in a giant dictionary? Because I still can’t quite understand this part of the code, not to say add more complexity:

                pos = Point3()
                pos[i] = direction
                pos[(i + direction) % 3] = a
                pos[(i + direction * 2) % 3] = b
                u, v = [pos[j] for j in range(3) if j != i]
                u *= (-1. if i == 1 else 1.) * direction
                uv = (max(0., u) / 6. + u_offset, max(0., v))

I understand it’s trying to add texture offsets but what is “pos”? If you could explain that part of code, that would be great!

Also, (stupid question) where am I supposed to add the texture to geom after loading the texture?

#2 The cracking animation needs to be finished start to finish or else it needs to start over. There wouldn’t be any time that multiple blocks needing to be animated the cracking animation. However, there could be multiple animations happening at the same time, i.e. multiple fires.

#3 ok, thank you.

#4 So if it is creating a new cube, does the original chunk model need to delete the original cube?

In fact, it was not hidden, the tile changes position, rising above the geometry. It’s strange to me why you can’t make it work.

Modified, there is a camera preset.

from panda3d.core import *
from random import randint
from direct.task.Task import Task

loadPrcFileData("editor-startup", "show-frame-rate-meter #t")
loadPrcFileData("editor-startup", "sync-video #f")

from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

    def __init__(self):
        self.trackball.node().set_pos(0, 30, 0)

        # Create
        node = GeomNode('gnode')
        geoms = []
        textures_count = 3

        for i in range(textures_count):
            gvd = GeomVertexData('name', GeomVertexFormat.getV3t2(), Geom.UHStatic)
            geom = Geom(gvd)
            prim = GeomTriangles(Geom.UHStatic)
            vertex = GeomVertexWriter(gvd, 'vertex')
            texcoord = GeomVertexWriter(gvd, 'texcoord')
            tex = loader.loadTexture('%i.png' % (i+1))

        for x in range(0,100):
            for z in range(0,100):
                t_img = randint(0,textures_count - 1)
                i = geoms[t_img]['index']
                geoms[t_img]['vertex'].addData3f(x, 0, z)
                geoms[t_img]['texcoord'].addData2f(0, 0)
                geoms[t_img]['vertex'].addData3f(x, 0, z+1)
                geoms[t_img]['texcoord'].addData2f(0, 1)
                geoms[t_img]['vertex'].addData3f(x+1, 0, z+1)
                geoms[t_img]['texcoord'].addData2f(1, 1)
                geoms[t_img]['vertex'].addData3f(x+1, 0, z)
                geoms[t_img]['texcoord'].addData2f(1, 0)
                d = i*4
                geoms[t_img]['prim'].addVertices(d, d + 2, d + 1)
                geoms[t_img]['prim'].addVertices(d, d + 3, d + 2)
                geoms[t_img]['index'] += 1

        for i in range(textures_count):
            node.setGeomState(i, node.getGeomState(i).addAttrib(TextureAttrib.make(geoms[i]['texture'])))

        terrain = render.attachNewNode(node)
        del geoms
        # Modify
        geom_blue = node.modifyGeom(2)
        self.vdata = geom_blue.modifyVertexData()

    def animation(self, task):

        vertex_blue = GeomVertexRewriter(self.vdata, 'vertex')
        v = vertex_blue.getData3f()
        vertex_blue.setData3f(v[0], v[1]-0.1*globalClock.get_dt(), v[2])
        v = vertex_blue.getData3f()
        vertex_blue.setData3f(v[0], v[1]-0.1*globalClock.get_dt(), v[2])
        v = vertex_blue.getData3f()
        vertex_blue.setData3f(v[0], v[1]-0.1*globalClock.get_dt(), v[2])
        v = vertex_blue.getData3f()
        vertex_blue.setData3f(v[0], v[1]-0.1*globalClock.get_dt(), v[2])
        return Task.cont

app = MyApp()

I tried again with a mouse and got it to work. But is it intended to be 2d? I can’t see the animation as well.

In fact, there is no difference between a flat geometry in the form of tiles or a cube. To be honest, I’m not going to implement mincraft as an example. :slightly_smiling_face:

Keep an eye on the first blue tile in the row.

1 Like

Nice! Again, thank you so much.

Yes, a texture atlas combines all of the different textures needed for the blocks into one big texture.

It might indeed be easier (especially in the beginning, when figuring out how it all works) to hard-code the UV-coordinates instead of using an algorithm that may look a bit “magical” :stuck_out_tongue: . The reason I use it is mainly to keep the code more compact, but you might prefer to write out the vertex data more explicitly, separately for each side of a block.

“pos” is the vertex position (x-, y- and z-coordinates), relative to the origin of the block. It is from this position that I derive the texture (UV) coordinates. Don’t worry if their relationship isn’t all that clear, since a different algorithm would be needed anyway (but like you suggested, it may be easier to just set the UVs to specific values that you get by dividing the pixel coordinates in the atlas by the size of the atlas).

You can add the texture to the NodePath that contains the “chunk” geometry (in my code sample it’s just self.model, so you call self.model.set_texture(my_texture_atlas)).

That’s OK then…

…but that could be problematic. It’s not obvious to me how different parts of one and the same geometry can have different animations applied to them, hmmm… Perhaps we shouldn’t worry about this yet and just take it one step at a time.

That’s not strictly necessary, no. The new cube will simply be a copy of the original one. In remove_cube, you can leave out the following lines:

        model_view[start:-cube_data_size] = model_view[end:]
        model_array.set_num_rows(model_vert_count - self.cube_vert_count)


        model_prim_view[start:-self.cube_prim_vert_count] = model_prim_view[end:]
        prim_array.set_num_rows(model_prim_vert_count - self.cube_prim_vert_count)

The original cube will remain part of the chunk model then.

…but that could be problematic. It’s not obvious to me how different parts of one and the same geometry can have different animations applied to them, hmmm… Perhaps we shouldn’t worry about this yet and just take it one step at a time.

Well, maybe each block with animation would be a seperate mesh. Such as fire would be it’s own mesh.

That’s not strictly necessary, no. The new cube will simply be a copy of the original one. In remove_cube, you can leave out the following lines:

Then won’t there be 2 of the same model to be seen?

OK, that should work.

Yes, what I meant was that you can do whatever you want with the original cube geometry within the chunk. You can remove it or leave it in, it’s up to you. It’s independent from the newly created cube node, which simply contains a copy of that original geometry.

Last question: what is uv? Is it the texture offset thing? Why is it giving 4 coordinates per face? I’m also assuming that the texture atlas are being scaled to 1x1.

Once again, thank you very much, not gonna repeat my appreciation message again :sweat_smile:. BTW how did you find out I understand Chinese.

This is explained quite well here and here; it might also help to read about texels. One thing to note about the relationship between a texel and its UV coordinates is that the vertical coordinate of a texel is usually measured from top to bottom in a paint program, while the corresponding texture coordinate (V) is measured from bottom to top. So the texel at (512, 256) in a 1024x1024 image will have its UVs at (0.5, 0.75).

The texture offset in my code just allows all six cube side textures to be placed side-by-side within the texture atlas.
Each face has four vertices, and each of those vertices needs to have different UVs.
Indeed, the UVs of the texture atlas range from 0 to 1, just like for any texture map.

Ah, in my recently upgraded crystal ball I saw you watching a Chinese drama without English subtitles :mage:!
Just kidding. Your username seemed like a good hint :wink: .

I probably should post this in Scripting section, but while we are here, I’m going to ask it anyway. Is it appropriate to use multiple ShowBases? More specifically, I am going to split the code into multiple classes, which all requires ShowBase to load texture. Thanks again!

Just pass self as an argument to the desired class.

from direct.showbase.ShowBase import ShowBase

class Test():
    def __init__(self, base_link):

        base_link.accept('enter', self.message)

    def message(self):

class MyApp(ShowBase):

    def __init__(self):

        myClass = Test(self)

app = MyApp()
1 Like
class A:
	def __init__(self):
		self.hi = 'hey'
		self.b = B(self)
class B:
	def __init__(self, global_self):
		self.global_self = global_self
	def check(self):
a = A()

Something like this? Why does this feel so trippy?

Thank you. I just thought of that inheritance should also work.

ShowBase creates a number of globally-accessible variables–including one called “base”, which holds a reference to the current ShowBase instance, and “loader”, which holds a reference to the current asset-loader. These variables should be available just about anywhere, meaning that you should in general not need multiple ShowBase instances in order to access things like texture-loading.

For example:

The game’s main Python file:

from CatFile import Cat

class MyGame(ShowBase):
    # Code for the class here...

myGame = MyGame()


class Cat():
    def __init__(self):
        # "loader" should be available here, being a global variable
        self.texture = loader.loadTexture("someTexture.png")
1 Like

Thank you. That solves my problem very nicely.

However, I still need to access “self.render” in “Cat”, without making the “MyGame” parent of “Cat”. Because that would cause to spawn multiple ShowBase instances(I will create multiple "Cat"s).

1 Like