Can i create multiple GeomNode sharing the same Geom?

Hi everyone,

I’m actuall making a small game and i’m doing everything from scratch. I’ve been able to create a hexagon grid following the manual on “Procedurally Generating 3D Models”. So i now have an hexagon grid made of Linestrip. The goal is to be able to clik on one of the hex and the game will create a surfaces (triStrips) at the click point.

Actually i have :

  • The Hex grid
  • The collision detection to know where the mouse has been clicked
  • A function that get the mouse postition during the “click” to create a new hex (with triangles) and set his position to the nearest hex center

I’m trying to create a new GeomNode everytime I use my mouse, but I don’t want to create another Geom since i already have one withe the right vertices and primitives : the one I used to make à single 1 sized hexagon.

I’ve look into the manual but can’t find what i’m looking for, I even try the “instanceTo” method, but it was of course a failure.

So if someone as an idea, I’ll be happy to hear your thought :slight_smile:

Mandatory : Bad english, mistakes, sorry :confused:

Hi, welcome to the community!

Yes, you can assign the same Geom to more than one GeomNode. Geom uses the copy-on-write paradigm, which means that a copy is not created until you try to modify one of them. This means Panda will still end up uploading the mesh only once to the GPU.

That does imply that if you call modifyGeom on one of the GeomNodes, it will create a unique copy of the Geom.

LOL. This is really funny. I was in the process of trying to do the same thing and thought maybe it could be done with textures, much as the “line art” in the asteroids demo. I thought that might be a better way to go since you could attach textures to the objects easily.

Naturally I was also considering the geometric method too, and finally decided to start with the geometric method, most importantly, because i want to have very tight control on the dimensions. You have to scale the textures and making them take on a certain dimension seemed tricky, especially since i am a beginner and not exactly sure about how scaling textures works (I’m not even sure if the .frag files in the asteroids demo are actually textures - they seem to be).

Would you mind sharing your hex generation code ? And if you can’t share the code, i would certainly appreciate any advice about how you did it, especially any problems you may have had implementing it. For example , the fact that you are making the hex grid with Linestrips is very useful to know.

Thanks!

You can do this by analogy with this example.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Point3D, deg2Rad, NodePath
from panda3d.egg import EggPolygon, EggVertexPool, EggData, EggVertex, loadEggData
import math

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        test = self.makeWedge()
        test.reparentTo(render)

    def makeWedge(self, angleDegrees = 360, numSteps = 6):
        data = EggData()

        vp = EggVertexPool('fan')
        data.addChild(vp)

        poly = EggPolygon()
        data.addChild(poly)

        v = EggVertex()
        v.setPos(Point3D(0, 0, 0))
        poly.addVertex(vp.addVertex(v))

        angleRadians = deg2Rad(angleDegrees)

        for i in range(numSteps + 1):
            a = angleRadians * i / numSteps
            y = math.sin(a)
            x = math.cos(a)

            v = EggVertex()
            v.setPos(Point3D(x, 0, y))
            poly.addVertex(vp.addVertex(v))

        # To write the egg file to disk, use this:
        #data.writeEgg(Filename("wedge.egg"))

        # To load the egg file and render it immediately, use this:
        node = loadEggData(data)
        return NodePath(node)

app = MyApp()
app.run()

I just tried it out, and it work great.
thank you !!
edit: so this does not use the the technique in the procedurally generated section of the manual correct ?

That’s a very neat trick to generate the .egg files procedurally.

If I may ask, where are you seeing these files? I don’t see them on my end, either in the GitHub repository or in the copy that I have locally.

(The only “.frag” files that I’m finding are in the “samples/shader-terrrain/” and “tests/display/” directories.)

However, without seeing the files themselves, I’d guess that they’re actually fragment-shaders, and not texture-files.

They are .png files and they are in the “textures” directory of asteroids.

The “models” directory has a single file "plane.egg’ which i assume sets up the background for the game (?)

Ah–a “.png” file is very different to a “.frag” file! That makes more sense, then, and indeed, those are textures I believe. Thank you for clarifying!

Looking at the code, no: it seems that “plane.egg” is used for both the background and the objects in the game. That is, each object–asteroid, ship, or bullet–is displayed via a model loaded from “plane.egg”, with the appropriate texture applied to make it look like whatever it’s supposed to be.

oh, ok. thank you for clarifying :wink:

1 Like

@tungsten
If you want the proper method, serega-kkz answer is the way to go. But since I wanted to try out the procedurally generated section of the manual I’m doing something like this (translated on the fly so be nice to my var name) :

from panda3d.core import GeomVertexFormat, GeomVertexData, Geom, GeomVertexWriter, GeomLinestrips, GeomNode
from direct.showbase.ShowBase import ShowBase
import math

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        hexGrid = self.drawGrid()
        hexagon_nodepath = self.render.attachNewNode(hexGrid)

#You can twist the surface to draw Hexagon on and their size here
    def drawGrid(self, start=(0, 0), end=(10, 10), HexSize=1):
        self.HexList = self.getGridData(start, end, HexSize)
        indexation = self.indexList(self.HexList)
        self.verticesList = indexation[0]
        self.HexsIndex = indexation[1]

        array = self.create_array(self.verticesList, 'Hexagon')

        geom = Geom(array)

        for hex in self.HexsIndex:
            prim = self.create_lineStrips_prim(hex)
            geom.add_primitive(prim)

        gridNode = GeomNode('Hex Grid')
        gridNode.addGeom(geom)

        return gridNode

    def getGridData(self, start, end, size):
        #This function calculate every vertices per hex that we need to have a grid
        firstx = start[0]
        firsty = start[1]
        actualy = firsty
        endx = end[0]
        endy = end[1]
        from_x_to_x = math.sqrt(3) * size
        from_y_to_y = (2 * size) * (3 / 4)
        x_offset = from_x_to_x / 2

        poly_grid = []
        row = 1

        while actualy < endy:
            if row % 2 == 0:
                actual_x = firstx + x_offset
            else:
                actual_x = firstx
            while actual_x < endx:
                poly = []
                for i in range(6):
                    angle_deg = 60 * i - 30
                    angle_rad = math.pi / 180 * angle_deg
                    x = actual_x + size * math.cos(angle_rad)
                    y = actualy + size * math.sin(angle_rad)
                    # If you don't round here, there is no optimisation done in a later step
                    poly.append((round(x, 4), round(y, 4)))
                poly.append(poly[0])
                poly_grid.append(poly)
                actual_x += from_x_to_x
            actualy += from_y_to_y
            row += 1
        return poly_grid

    def indexList(self, HexList):
        #Here we're doing 2 things : Firts we create a new list of vertices with no double; and at the same time we're collecting the indexes to create our lineStrip later
        new_list = []
        hexIndex = []
        for hexagon in HexList:
            actual_hex = []
            for vertices in hexagon:
                if vertices in new_list:
                    # If we haven't round earlier, this won't work, we'll just gonna copy the list.
                    actual_hex.append(new_list.index(vertices))
                else :
                    new_list.append(vertices)
                    actual_hex.append(new_list.index(vertices))
            hexIndex.append(actual_hex)
        return new_list, hexIndex

    def create_array(self, list_of_points, array_name='unknown'):
        #Here we're creating the GeomVertexData and Writer, then we feed them our vertices
        type_of_array = array_name
        number_of_row = len(list_of_points)
        array_format = GeomVertexFormat.get_v3()
        vdata = GeomVertexData(type_of_array, array_format, Geom.UHStatic)
        vdata.setNumRows(number_of_row)
        vertex = GeomVertexWriter(vdata, 'vertex')
        for point in list_of_points:
            vertex.addData3(point[0], 0, point[1])
        return vdata

#Here you can twist this function to have it draw TriStrip if that's what you want by picking the indexes [2, 3, 1, 4, 0, 5] instead of the order we send them in
    def create_lineStrips_prim(self, HexIndex):

        prim = GeomLinestrips(Geom.UHStatic)
        for point in HexIndex:
            prim.add_vertex(point)
        prim.close_primitive()
        return prim

app = MyApp()
app.run()

I then save the node with node.writeBamFile, since i’m not really interested on saving the model, but serega-kkz answer is safer to store your model long terme
I’ve try my best to be easy to understand, feel free to ask if something isn’t clear of if you have trouble with the sample code.

@rdb

That does imply that if you call modifyGeom on one of the GeomNodes, it will create a unique copy of the Geom.

Thanks for your answer ! This is working in the sense that it create a new GeomNode with the same Geom, but since I’m creating à copy of my first Geom I actually end with two of them (each new Hex increase my Geom counter by one on the scene graph analyzer). I’m wondering if it’s possible to add a new GeomNode without increasing my Geom count at all.

If you create a copy of a GeomNode, it won’t copy the Geoms therein unless you try to modify them.

An alternative approach is to use instancing—which really means that you just parent the same GeomNode to more than one parent node (you can do this directly by using .node().addChild() instead of .reparentTo(), or you can use instanceTo).

Thanks so much!

your code works, but one thing i’ve noticed about your code and @serega-kkz code is that the viewport starts out funny. Initially i can’t see anything until I zoom using the right mouse button. It looks as though the camera is zoomed way in, i.e. i have to zoom out to make the grid appear.

I’m just wondering if that’s what happens when you run it or if there is something funny about my panda set-up.

Just the camera and the model are in the same position. You just move the camera away with the right mouse button.

A small fix that allows you to save the model correctly.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import Point3D, deg2Rad, NodePath, Filename
from panda3d.egg import EggPolygon, EggVertexPool, EggData, EggVertex, loadEggData, EggCoordinateSystem
import math

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        # Creating egg data
        data = self.makeWedge()

        # To write the egg file to disk, use this:
        #data.writeEgg(Filename("wedge.egg"))

        # To load the egg file and render it immediately, use this:
        model = NodePath(loadEggData(data))
        model.reparentTo(render)

    def makeWedge(self, angleDegrees = 360, numSteps = 6):

        z_up = EggCoordinateSystem()
        z_up.setValue(1)

        data = EggData()
        data.addChild(z_up)

        vp = EggVertexPool('fan')
        data.addChild(vp)

        poly = EggPolygon()
        data.addChild(poly)

        v = EggVertex()
        v.setPos(Point3D(0, 0, 0))
        poly.addVertex(vp.addVertex(v))

        angleRadians = deg2Rad(angleDegrees)

        for i in range(numSteps + 1):
            a = angleRadians * i / numSteps
            y = math.sin(a)
            x = math.cos(a)

            v = EggVertex()
            v.setPos(Point3D(x, 0, y))
            poly.addVertex(vp.addVertex(v))

        return data

app = MyApp()
app.run()

@tungsten if you have a comment that’s a new question and not an answer to the topic poster’s question, it is better if you create a new thread.

@rdb Thanks for the answer, but if so, my problem may not exist at all. What is bothering me is the geom counter going up on the scene graph analyzer. For example if i do :

        hexA = myGeomNode
        hexA_nodepath = render.attachNewNode(hexA)

       hex_copy  = hexA_nodepath.copyTo(render)

        hexB_nodepath  = render.attachNewNode("other Hex")
        hexA_nodepath.instanceTo(hexB_nodepath)

        hex_copy.setPos(0,0,-2)
        hexB_nodepath.setPos(0,0,2)

Everything is exactly what i expect, but the scene graph analyzer says that I have 3Geoms and 3 GeomNode. Is this because it count the geom copy and geom instance even if panda “doesn’t compute” them ?

I think it is counting the number of times the Geom is being visited by the renderer, so it’s counting the same Geom more than once.

I’d be curious about the geom count in render.analyze(), though.

Thanks for the clarification.

Here’s the render.analyse() outpout for reference :

7 total nodes (including 1 instances); 0 LODNodes.
2 transforms; 14% of nodes have some render attribute.
3 Geoms, with 1 GeomVertexDatas and 1 GeomVertexFormats, appear on 3 GeomNodes.
6 vertices, 0 normals, 0 colors, 0 texture coordinates.
GeomVertexData arrays occupy 1K memory.
GeomPrimitive arrays occupy 1K memory.
12 triangles:
  12 of these are on 3 tristrips (4 average tris per strip).
  0 of these are independent triangles.
0 textures, estimated minimum 0K texture memory required.