Exemple #1
0
 def traverse(self, nodePath, dnaStorage):
     sign = dnaStorage.findNode(self.code)
     if not sign:
         sign = NodePath(self.name)
     signOrigin = nodePath.find('**/*sign_origin')
     if not signOrigin:
         signOrigin = nodePath
     node = sign.copyTo(signOrigin)
     #node.setDepthOffset(50)
     node.setPosHprScale(signOrigin, self.pos, self.hpr, self.scale)
     node.setPos(node, 0, -0.1, 0)
     node.setColor(self.color)
     for child in self.children_:
         child.traverse(node, dnaStorage)
     node.flattenStrong()
Exemple #2
0
 def traverse(self, nodePath, dnaStorage):
     sign = dnaStorage.findNode(self.code)
     if not sign:
         sign = NodePath(self.name)
     signOrigin = nodePath.find('**/*sign_origin')
     if not signOrigin:
         signOrigin = nodePath
     node = sign.copyTo(signOrigin)
     node.setDepthOffset(50)
     node.setPosHprScale(signOrigin, self.pos, self.hpr, self.scale)
     #node.setPos(node, 0, -0.1, 0)
     node.setColor(self.color)
     for child in self.children_:
         child.traverse(node, dnaStorage)
     node.flattenStrong()
Exemple #3
0
class Menu(object):
    '''
    '''
    def __init__(self, parent):
        
        self._notify = DirectNotify().newCategory("Menu")
        self._notify.info("New Menu-Object created: %s" %(self))
        
        #Font
        self.font = DynamicTextFont(FONT)
        self.font.setRenderMode(TextFont.RMSolid)
        
        self.KEY_DELAY = 0.15
        self.player_buttonpressed = []
        
        self._parent = parent
        self._players = parent.players
        self._devices = parent.devices
        
        taskMgr.add(self.fetchAnyKey, "fetchAnyKey")
        
    def showStartScreen(self):
        '''
        the first screen with "press any Key"
        the device with the first key press will be the first player
        '''
        self._notify.info("Initializing StartScreen")
        
        self.wii = []
        
        #StartScreen Node
        self.startNode = NodePath("StartNode")
        self.startNode.reparentTo(render)
        self.startNode.setPos(-5,15,3)

        #Headline model
        headline = loader.loadModel("data/models/logo.egg")
        headline.setX(4.7)
        headline.setY(-4)
        headline.setZ(-2)
        headline.setP(90)
        headline.reparentTo(self.startNode)
        
        #Press any key text
        presskey = TextNode("PressAnyKey")
        presskey.setFont(self.font)
        presskey.setText(_("Press any key!!"))
        textNodePath = NodePath("PressAnyNode")
        textNodePath.attachNewNode(presskey)
        textNodePath.setPos(0,10,-9.5)
        textNodePath.reparentTo(self.startNode)
        
        #Show the start screen 
        self.startNode.show()
        
        #LICHT
        plight = PointLight('plight')
        plight.setColor(VBase4(10, 10, 10, 1))
        plnp = self.startNode.attachNewNode(plight)
        plnp.setPos(20, -800, 30)
        self.startNode.setLight(plnp)
        
        #Camera
        self.camera = base.makeCamera(base.win)
        
        self._notify.info("StarScreen initialized")
        
    # -----------------------------------------------------------------
    
    def fetchAnyKey(self, task):
        '''
        Return the first device with the first key stroke
        '''
        for i in xrange(len(self._devices.devices)):
            if self._devices.devices[i].boost:
                #Kill Camera
                self.camera.node().setActive(False)
                #Kill Node
                self.startNode.removeNode()
                
                #Start the menu
                self.menu = MainMenu(self._parent, self.newGame, self._devices.devices[i], self._devices)
                self.menu.menuMain()
                return task.done
        return task.cont



        # -----------------------------------------------------------------

    def newGame(self):
        '''
        The select vehicle screen
        '''
        base.enableParticles()
        self.countdown = COUNTDOWN_START #the countdown, when its over the game can be started
        self._notify.info("Initializing new game")
        #GlobPattern if we need a Panda Class
        self.vehicle_list = glob.glob("data/models/vehicles/*.egg")
        for index in range(len(self.vehicle_list)):
            self.vehicle_list[index] = Filename.fromOsSpecific(self.vehicle_list[index]).getFullpath()
        self._notify.debug("Vehicle list: %s" %(self.vehicle_list))
        self.platform = loader.loadModel("data/models/platform.egg")
        
        #Loading-Text that gets displayed when loading a model
        text = TextNode("Loading")
        text.setFont(self.font)
        text.setText(_("Loading..."))
        text.setAlign(TextProperties.ACenter)
        self.loading = NodePath("LoadingNode")
        self.loading.attachNewNode(text)
        self.loading.setPos(0,0,2)
        
        #The countdown, its possible to start the game when its 0
        text = TextNode("Countdown")
        text.setFont(self.font)
        text.setAlign(TextProperties.ACenter)
        text.setText(_(str(COUNTDOWN_START)))
        self.countdown_node = NodePath("Countdown")
        self.countdown_node.attachNewNode(text)
        self.countdown_node.setPos(0,0,4)
        
        #PreLoad the description that gets displayed when loading a model
        text = TextNode("name")
        text.setFont(self.font)
        text.setText(_("Loading..."))
        text.setAlign(TextProperties.ACenter)
        self.attributes = NodePath("AttributeNode")
        self.attributes.attachNewNode(text)
        
        self.unusedDevices = self._devices.devices[:]
        taskMgr.add(self.collectPlayer, "collectPlayer")
        #taskMgr.add(self.collectWii, "collectWii")
        self.screens = []
        taskMgr.add(self.selectVehicle, "selectVehicle")
        
        self.color_red = Vec4(1,0,0,0)
        self.color_green = Vec4(0,1,0,0)
        
        self._notify.info("New game initialized")
        
    # -----------------------------------------------------------------
        
    def selectVehicle(self, task):
        '''
        The vehicle select and rotate task
        '''
        #Set the countdown and hide, if > 3
        self.countdown -= globalClock.getDt()
        if self.countdown <=0:
            self.countdown_node.getChild(0).node().setText(_("Go"))
            self.countdown_node.setColor(self.color_green)
        elif self.countdown <=3: 
            self.countdown_node.getChild(0).node().setText(str(int(round((self.countdown+0.5)))))
            self.countdown_node.setColor(self.color_red)           
            self.countdown_node.show()
        else: self.countdown_node.hide()
        
        heading = self.loading.getH() -(30 * globalClock.getDt())
        self.loading.setH(heading)
        for player in self._players:
            if player.vehicle.model != None:
                player.vehicle.model.setH(heading)
                
            if self.player_buttonpressed[self._players.index(player)] < task.time and not player.vehicle.model_loading:
                if player.device.directions[0] < -0.8:
                    self._parent.menuselect.play()
                    self.countdown = COUNTDOWN_START
                    self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY
                    index = self.vehicle_list.index("data/models/vehicles/%s" %(player.vehicle.model.getName()))-1
                    self._notify.debug("Previous vehicle selected: %s" %(index))
                    player.vehicle.model_loading = True
                    player.vehicle.model.hide()
                    player.camera.camera.getParent().find("AttributeNode").hide()#Hide the attributes
                    self.loading.instanceTo(player.camera.camera.getParent())
                    loader.loadModel(self.vehicle_list[index], callback = player.setVehicle)
                elif player.device.directions[0] > 0.8:
                    self._parent.menuselect.play()
                    self.countdown = COUNTDOWN_START
                    self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY
                    index = self.vehicle_list.index("data/models/vehicles/%s" %(player.vehicle.model.getName()))+1
                    self._notify.debug("Next vehicle selected: %s" %(index))
                    if index >= len(self.vehicle_list): index = 0
                    player.vehicle.model_loading = True
                    player.vehicle.model.hide()
                    player.camera.camera.getParent().find("AttributeNode").hide()#Hide the attributes
                    self.loading.instanceTo(player.camera.camera.getParent())
                    loader.loadModel(self.vehicle_list[index], callback = player.setVehicle)
                    
                if player.device.directions[1] > 0.8:
                    self.countdown = COUNTDOWN_START
                    self._parent.menuselect.play()
                    self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY
                 
                    self._notify.debug("Next color selected")
                    a = player.vehicle.model.findAllTextures()
                    tex = a.findTexture(player.vehicle.model.getName()[:-4])
                    self.rotateHue(tex, 0.1)
                    
                elif player.device.directions[1] < -0.8:
                    self._parent.menuselect.play()
                    self.countdown = COUNTDOWN_START
                    self.player_buttonpressed[self._players.index(player)] = task.time + self.KEY_DELAY
                 
                    self._notify.debug("Next color selected")
                    a = player.vehicle.model.findAllTextures()
                    tex = a.findTexture(player.vehicle.model.getName()[:-4])
                    self.rotateHue(tex, -0.1)
        return task.cont
    
    # -----------------------------------------------------------------

    def rotateHue(self, tex, value=0.1):
        '''
        '''
        img = PNMImage()
        tex.store(img)
        for y in xrange(img.getReadYSize()):
            for x in xrange(img.getReadXSize()):
                r, g, b = img.getXel(x,y)
                h, s, v = colorsys.rgb_to_hsv(r, g, b)
                h += value
                if h < 0:
                    h += 360
                r, g, b = colorsys.hsv_to_rgb(h, s, v)
                img.setXel(x,y,r,g,b)
        tex.load(img)
    
    # -----------------------------------------------------------------

    def collectWii(self, task):
        '''
        Collect wiimotes task
        '''
        try:
            print 'Put Wiimote in discoverable mode now (press 1+2)...'
            wiimote = cwiid.Wiimote()
            self.wiimoteX.append(wiimote)
            print len(self.wiimoteX)
        except:
            pass
        return task.cont
        
    # -----------------------------------------------------------------
     
    def collectPlayer(self, task):
        '''
        Wait until all players are ready
        '''
        if len(self._players) > 0 and self.player_buttonpressed[0] < task.time:
            if self._players[0].device.boost and self.countdown <= 0:
                loading = False
                for player in self._players: 
                    if player.vehicle.model_loading: 
                        loading = True
                        break
                self._notify.debug("Loading vehicle: %s" %(loading))
                if not loading:
                    taskMgr.remove("selectVehicle")
                    self.track =  trackgen3d.Track3d(1000, 1800, 1600, 1200, 5)#len(self._players))
                    self.streetPath = render.attachNewNode(self.track.createRoadMesh())
                    #self.borderleftPath = render.attachNewNode(self.track.createBorderLeftMesh())
                    self.borderleftPath = render.attachNewNode(self.track.createBorderLeftMesh())
                    self.borderrightPath = render.attachNewNode(self.track.createBorderRightMesh())
                    self.borderleftcollisionPath = NodePath(self.track.createBorderLeftCollisionMesh())
                    self.borderrightcollisionPath = NodePath(self.track.createBorderRightCollisionMesh())
                    ##self.borderPath = render.attachNewNode(self.track.createBorderMesh())
                    
                    textures = ["tube", "tube2", "street"]
                    tex = textures[random.randint(0, len(textures)-1)]
                    roadtex = loader.loadTexture('data/textures/'+tex+'.png')
                    bordertex = loader.loadTexture('data/textures/border.png')
                    self.streetPath.setTexture(roadtex)
                    self.borderleftPath.setTexture(bordertex)
                    self.borderrightPath.setTexture(bordertex)

                    #self.streetPath = loader.loadModel('data/models/Street.egg')
                    ##self.streetPath = loader.loadModel('data/models/Street.egg')
                    #tex = loader.loadTexture('data/models/StreetTex.png')
                    #self.nodePath.setTexture(tex)
                    
                    self._parent.startGame(self.streetPath,self.borderleftPath,self.borderrightPath, self.track.trackpoints, self.borderleftcollisionPath, self.borderrightcollisionPath)
                    return task.done

        for device in self.unusedDevices:
                if device.boost:
                    self.countdown = COUNTDOWN_START
                    self.player_buttonpressed.append(0)
                    self._parent.addPlayer(device)
                    
                    #Set the PlayerCam to the Vehicle select menu Node        
                    vehicleSelectNode = NodePath("VehicleSelectNode")
                    self._players[-1].camera.camera.reparentTo(vehicleSelectNode)
                    
                    #Light, that casts shadows
                    plight = Spotlight('plight')
                    plight.setColor(VBase4(10.0, 10.0, 10.0, 1))
                    if (base.win.getGsg().getSupportsBasicShaders() != 0):
                        pass
                        ##plight.setShadowCaster(True, 2048, 2048)#enable shadows for this light ##TODO wegen Linux
                        
                    #Light
                    plight.getLens().setFov(80)
                    plnp = vehicleSelectNode.attachNewNode(plight)
                    plnp.setPos(2, -10, 10)
                    plnp.lookAt(0,0,0)
                    vehicleSelectNode.setLight(plnp)
##                    vehicleSelectNode.setShaderAuto()#enable autoshader so we can use shadows
                    
                    #Light
                    ambilight = AmbientLight('ambilight')
                    ambilight.setColor(VBase4(0.2, 0.2, 0.2, 1))
                    vehicleSelectNode.setLight(vehicleSelectNode.attachNewNode(ambilight))
                    self.platform.instanceTo(vehicleSelectNode) #Load the platform
                    
                    #instance shown text
                    self.countdown_node.instanceTo(vehicleSelectNode) #Instance the Countdown
                    self.loading.instanceTo(vehicleSelectNode) #Show the Loading-Text
                    self.attributes.copyTo(vehicleSelectNode).hide()
                    self._players[-1].vehicle.model_loading = True
                    
                    #start loading the model
                    loader.loadModel(self.vehicle_list[0], callback = self._players[-1].setVehicle)
                    self._notify.debug("Loading initial vehicle: %s" %(self.vehicle_list[0]))
                    self.unusedDevices.remove(device) 
                    self.player_buttonpressed[-1] = task.time + self.KEY_DELAY
                    
                    #Add the Skybox
                    skybox = loader.loadModel("data/models/skybox.egg")
                    t = Texture()
                    t.load(PNMImage("data/textures/skybox_hangar.png"))
                    skybox.setTexture(t)
                    skybox.setBin("background", 1)
                    skybox.setDepthWrite(0)
                    skybox.setDepthTest(0)
                    skybox.setLightOff()
                    skybox.setScale(10000)
                    skybox.reparentTo(vehicleSelectNode)

        for player in self._players:
            if self.player_buttonpressed[self._players.index(player)] < task.time:
                if player.device.use_item:
                    self.countdown = COUNTDOWN_START
                    self._notify.debug("Removing player: %s" %(player))
                    self.unusedDevices.append(player.device)
                    self.player_buttonpressed.pop(self._players.index(player))
                    self._parent.removePlayer(player) 
        return task.cont
Exemple #4
0
    def update(self, task):
        if not self.shouldRender():
            self.removeCurrentGrid()
            return task.cont

        zoom = self.calcZoom()
        step = GridSettings.DefaultStep
        low = GridSettings.Low
        high = GridSettings.High
        actualDist = step * zoom
        if GridSettings.HideSmallerToggle:
            while actualDist < GridSettings.HideSmallerThan:
                step *= GridSettings.HideFactor
                actualDist *= GridSettings.HideFactor

        if step == self.lastStep and self.gridNp:
            return task.cont

        self.removeCurrentGrid()

        self.lastStep = step

        if step in self.gridsByStep:
            self.gridNp = self.gridsByStep[step].copyTo(self.viewport.gridRoot)
            return task.cont

        segs = LineSegs()
        i = low
        while i <= high:
            color = GridSettings.GridLines
            if i == 0:
                # On zero lines, give each axis an appropriate color.
                axes = self.viewport.getGridAxes()
                color = Vec4(0, 0, 0, 1)
                color[axes[0]] = 1
                color2 = Vec4(0, 0, 0, 1)
                color2[axes[1]] = 1
            #elif (i % GridSettings.Highlight2Unit) == 0 and GridSettings.Highlight2Toggle:
            #    color = GridSettings.Highlight2
            elif (i % (step * GridSettings.Highlight1Line)
                  == 0) and GridSettings.Highlight1Toggle:
                color = GridSettings.Highlight1
            segs.setColor(color)
            segs.moveTo(self.viewport.expand(Point3(low, 0, i)))
            segs.drawTo(self.viewport.expand(Point3(high, 0, i)))
            if i == 0:
                segs.setColor(color2)
            segs.moveTo(self.viewport.expand(Point3(i, 0, low)))
            segs.drawTo(self.viewport.expand(Point3(i, 0, high)))
            i += step

        #segs.setColor(GridSettings.BoundaryLines)
        # top
        #segs.moveTo(self.viewport.expand(Point3(low, 0, high)))
        #segs.drawTo(self.viewport.expand(Point3(high, 0, high)))
        # left
        #segs.moveTo(self.viewport.expand(Point3(low, 0, low)))
        #segs.drawTo(self.viewport.expand(Point3(low, 0, high)))
        # right
        #segs.moveTo(self.viewport.expand(Point3(high, 0, low)))
        #segs.drawTo(self.viewport.expand(Point3(high, 0, high)))
        # bottom
        #segs.moveTo(self.viewport.expand(Point3(low, 0, low)))
        #segs.drawTo(self.viewport.expand(Point3(high, 0, low)))

        np = NodePath(segs.create())
        #np.setAntialias(AntialiasAttrib.MLine)
        #loader.loadModel("models/smiley.egg.pz").reparentTo(np)
        self.gridsByStep[step] = np
        self.gridNp = np.copyTo(self.viewport.gridRoot)

        return task.cont
Exemple #5
0
class VoxelGenerator(DirectObject):
    def __init__(self, parent, level, chunk_size=(7, 7, 1)):
        self.parent = parent
        self.level = level
        self.node = render.attachNewNode('main_level_node')
        self.chunk_size = chunk_size
        self.node_wall_original = NodePath('wall_original')
        self.node_wall_usable = self.node.attachNewNode("voxgen_wall_usable")
        self.node_forcewall_usable = self.node.attachNewNode(
            "voxgen_force_wall_usable")
        self.node_dynamic_wall_usable = self.node.attachNewNode(
            "voxgen_dynamic_wall_usable")
        self.wall_dict = {}
        self.dynamic_wall_dict = {}

        # For floors we will create a list of nodepaths, each list member will parent one chunk of tiles
        x = int((level.maxX - 1) / chunk_size[0]) + 1
        y = int((level.maxY - 1) / chunk_size[1]) + 1
        self.floor_np = NodePath("voxgen_floor_original")
        self.floor_usable_np = self.node.attachNewNode("voxgen_floor_usable")
        self.floor_np_list = []
        self.floor_usable_np_list = []
        for i in xrange(x):
            temp_list = []
            temp_list_usable = []
            for j in xrange(y):
                n = self.floor_np.attachNewNode('n_' + str(i) + '_' + str(j))
                temp_list.append(n)
                n2 = self.floor_usable_np.attachNewNode('n_usable_' + str(i) +
                                                        '_' + str(j))
                temp_list_usable.append(n2)
            self.floor_np_list.append(temp_list)
            self.floor_usable_np_list.append(temp_list_usable)

        self.floor_tile_dict = {}

        # We need a FIFO struct to hold pointers to dirty chunks that need flattening
        self.dirty_chunks = deque()
        self.dirty_walls = 0

        self.old_tile_dict = None
        self.old_invisible_walls = []

        # Create flatten task
        self.pause_flatten_task = True
        self.frames_between = 3
        self.frames_counter = 0
        taskMgr.add(self.flattenTask, "flatten_task")
        self.grid_display = False
        self.createGrid()
        self.accept('z', self.toggleGrid)

    def createGrid(self):
        segs = LineSegs()
        segs.setThickness(4.0)
        segs.setColor(Vec4(1, 1, 0, 0.3))
        for i in xrange(self.level.maxX):
            segs.moveTo(i + 1, 0, utils.GROUND_LEVEL)
            segs.drawTo(i + 1, self.level.maxY, utils.GROUND_LEVEL + 0.02)
        for j in xrange(self.level.maxY):
            segs.moveTo(0, j + 1, utils.GROUND_LEVEL)
            segs.drawTo(self.level.maxX, j + 1, utils.GROUND_LEVEL + 0.02)
        self.grid = NodePath(segs.create())
        self.grid.setTransparency(TransparencyAttrib.MAlpha)

    def toggleGrid(self):
        if not self.grid_display:
            self.grid.reparentTo(self.node)
            self.grid_display = True
        else:
            self.grid.detachNode()
            self.grid_display = False

    def markChunkDirty(self, x, y):
        chunk_x = int(x / self.chunk_size[0])
        chunk_y = int(y / self.chunk_size[0])
        payload = (self.floor_np_list[chunk_x][chunk_y], chunk_x, chunk_y)
        if not payload in self.dirty_chunks:
            self.dirty_chunks.append(payload)

    def markAllChunksDirty(self):
        self.pause_flatten_task = True
        for idx, val in enumerate(self.floor_np_list):
            for idy, v in enumerate(val):
                payload = self.floor_np_list[idx][idy], idx, idy
                self.dirty_chunks.append(payload)
        self.pause_flatten_task = False

    def createLevel(self):
        self.tex1 = loader.loadTexture("tile1.png")
        self.tex2 = loader.loadTexture("tile2.png")
        self.tex3 = loader.loadTexture("tile3.png")
        self.tex_tile_nm = loader.loadTexture("tile2nm.png")
        self.tex_fs = loader.loadTexture("rnbw.png")
        self.ts_fs = TextureStage('ts_fs')

        self.tex1.setMagfilter(Texture.FTLinearMipmapLinear)
        self.tex1.setMinfilter(Texture.FTLinearMipmapLinear)
        self.tex2.setMagfilter(Texture.FTLinearMipmapLinear)
        self.tex2.setMinfilter(Texture.FTLinearMipmapLinear)
        self.tex3.setMagfilter(Texture.FTLinearMipmapLinear)
        self.tex3.setMinfilter(Texture.FTLinearMipmapLinear)
        self.tex_tile_nm.setMagfilter(Texture.FTLinearMipmapLinear)
        self.tex_tile_nm.setMinfilter(Texture.FTLinearMipmapLinear)

        ts = TextureStage('ts')
        for x in xrange(0, self.level.maxX):
            for y in xrange(0, self.level.maxY):
                if self.level.getHeight((x, y)) == 0:

                    model = loader.loadModel('flattile')
                    model.setScale(utils.TILE_SIZE)
                    model.setPos(x * utils.TILE_SIZE, y * utils.TILE_SIZE,
                                 utils.GROUND_LEVEL)
                    """
                    if (x == 0 or x == self.level.maxX-1) or (y==0 or y==self.level.maxY-1):
                        model = loader.loadModel('halfcube')
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(x*utils.TILE_SIZE, y*utils.TILE_SIZE, utils.GROUND_LEVEL)
                    else:
                        model = loader.loadModel('flattile')
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(x*utils.TILE_SIZE, y*utils.TILE_SIZE, utils.GROUND_LEVEL)
                    """
                    #model = loader.loadModel('halfcube')
                    #model.setPos(x, y, 0)
                    model.reparentTo(self.floor_np_list[int(
                        x / self.chunk_size[0])][int(y / self.chunk_size[1])])
                    self.floor_tile_dict[(x, y)] = model
                elif self.level.getHeight((x, y)) == 1:
                    model = loader.loadModel('halfcube')
                    model.setScale(utils.TILE_SIZE)
                    model.setPos(x * utils.TILE_SIZE, y * utils.TILE_SIZE, 0)
                    model.reparentTo(self.floor_np_list[int(
                        x / self.chunk_size[0])][int(y / self.chunk_size[1])])
                    self.floor_tile_dict[(x, y)] = model
                    model = loader.loadModel('halfcube')
                    model.setScale(utils.TILE_SIZE)
                    model.setPos(x * utils.TILE_SIZE, y * utils.TILE_SIZE,
                                 utils.GROUND_LEVEL)
                    model.setTexture(ts, self.tex1)
                    #model.setTexture(self.ts_nm, self.tex_tile_nm)
                    model.reparentTo(self.node_wall_original)
                else:
                    for i in xrange(0, self.level.getHeight((x, y))):
                        if i == 0:

                            if (x == 0 or x == self.level.maxX -
                                    1) or (y == 0 or y == self.level.maxY - 1):
                                model = loader.loadModel('halfcube')
                                model.setScale(utils.TILE_SIZE)
                                model.setPos(x * utils.TILE_SIZE,
                                             y * utils.TILE_SIZE,
                                             utils.GROUND_LEVEL)
                            else:
                                model = loader.loadModel('flattile')
                                model.setScale(utils.TILE_SIZE)
                                model.setPos(x * utils.TILE_SIZE,
                                             y * utils.TILE_SIZE,
                                             utils.GROUND_LEVEL)

                            model.reparentTo(self.floor_np_list[int(
                                x / self.chunk_size[0])][int(
                                    y / self.chunk_size[1])])
                            self.floor_tile_dict[(x, y)] = model
                        else:
                            model = loader.loadModel('cube')
                            model.setScale(utils.TILE_SIZE)
                            model.setPos(
                                x * utils.TILE_SIZE, y * utils.TILE_SIZE,
                                (i - 1) * utils.TILE_SIZE + utils.GROUND_LEVEL)
                            model.setTexture(ts, self.tex2)
                            #model.setTexture(self.ts_nm, self.tex_tile_nm)
                            model.reparentTo(self.node_wall_original)
        self.floor_np.setTexture(self.tex3)

        #Calculate and place walls between tiles
        for x, val in enumerate(self.level._grid):
            for y, val2 in enumerate(val):
                if val2 != None:
                    # prvi parni, drugi neparni
                    tile2_x, tile2_y, h = self.getWallPosition(x, y)
                    if tile2_x == None:
                        continue

                    my_x = tile2_x
                    my_y = tile2_y
                    if val2.name == "Wall1":
                        model = loader.loadModel("wall")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        self.wall_dict[(my_x, my_y, h)] = model
                        model.reparentTo(self.node_wall_original)
                    elif val2.name == "Wall2":
                        model = loader.loadModel("wall2")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setColor(0, 1, 0, 1)
                        self.wall_dict[(my_x, my_y, h)] = model
                        model.reparentTo(self.node_wall_original)
                    elif val2.name == "HalfWall":
                        model = loader.loadModel("wall2")
                        model.setScale(utils.TILE_SIZE)
                        model.flattenLight()
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setColor(0, 0, 0, 1)
                        model.setScale(1, 1, 0.4)
                        self.wall_dict[(my_x, my_y, h)] = model
                        model.reparentTo(self.node_wall_original)
                    elif val2.name == "Ruin":
                        model = loader.loadModel("wall2")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setColor(0.5, 0.8, 1, 0.6)
                        model.setTransparency(TransparencyAttrib.MAlpha)
                        model.reparentTo(self.node_forcewall_usable)
                        s = Sequence(
                            LerpColorInterval(model, 1,
                                              (0.13, 0.56, 0.78, 0.6)),
                            LerpColorInterval(model, 1, (0.5, 0.8, 1, 0.6)),
                        )
                        s.loop()
                        model.setLightOff()
                    elif val2.name == "ClosedDoor":
                        model = loader.loadModel("door")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setColor(1, 0.0, 0, 0.0)
                        self.dynamic_wall_dict[(my_x, my_y, h)] = model
                        model.reparentTo(self.node_dynamic_wall_usable)
                    elif val2.name == "OpenedDoor":
                        model = loader.loadModel("door")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setScale(0.2, 1, 1)
                        model.setColor(0.7, 0.2, 0.2, 0.0)
                        self.dynamic_wall_dict[(my_x, my_y, h)] = model
                        model.reparentTo(self.node_dynamic_wall_usable)
                    elif val2.name == "ForceField":
                        model = loader.loadModel("wall_fs")
                        model.setScale(utils.TILE_SIZE)
                        model.setPos(my_x * utils.TILE_SIZE,
                                     my_y * utils.TILE_SIZE,
                                     utils.GROUND_LEVEL)
                        model.setH(h)
                        model.setTexture(self.ts_fs, self.tex_fs)
                        model.setTransparency(TransparencyAttrib.MAlpha)
                        model.reparentTo(self.node_forcewall_usable)
                        self.dynamic_wall_dict[(my_x, my_y, h)] = model
                        model.setLightOff()

        #self.floor_usable_np.setShaderAuto()
        self.floor_usable_np.setTexture(self.tex3)
        self.markAllChunksDirty()
        self.dirty_walls = 1

    def getWallPosition(self, x, y):
        if (x % 2 == 0 and y % 2 != 0):
            pos_x = x / 2
            pos_y = (y - 1) / 2
            h = 90
        # prvi neparni, drugi parni
        elif (x % 2 != 0 and y % 2 == 0):
            pos_x = (x - 1) / 2
            pos_y = y / 2
            h = 0
        else:
            pos_x = None
            pos_y = None
            h = None
        return pos_x, pos_y, h

    def processLevel(self, invisible_walls):
        self.level = self.parent.parent.level
        for x, val in enumerate(self.level._grid):
            for y, val2 in enumerate(val):
                if val2 != None:
                    my_x, my_y, h = self.getWallPosition(x, y)
                    # prvi parni, drugi neparni
                    if my_x == None:
                        continue

                    if val2.name == "ClosedDoor" and val2.name != self.parent.parent.old_level._grid[
                            x][y].name:
                        if not (x, y) in self.old_invisible_walls:
                            self.dynamic_wall_dict[(my_x, my_y, h)].setScale(1)
                        else:
                            i = self.dynamic_wall_dict[(my_x, my_y,
                                                        h)].scaleInterval(
                                                            1, Vec3(1, 1, 1))
                            i.start()
                    elif val2.name == "OpenedDoor" and val2.name != self.parent.parent.old_level._grid[
                            x][y].name:
                        if not (x, y) in self.old_invisible_walls:
                            self.dynamic_wall_dict[(my_x, my_y,
                                                    h)].setScale(0.2, 1, 1)
                        else:
                            i = self.dynamic_wall_dict[(my_x, my_y,
                                                        h)].scaleInterval(
                                                            1, Vec3(0.2, 1, 1))
                            i.start()
        self.old_invisible_walls = invisible_walls

    def setInvisibleTilesInThread(self):
        taskMgr.add(self.setInvisibleTiles,
                    'invis_tiles',
                    extraArgs=[],
                    taskChain='thread_1')

    def setInvisibleTiles(self):
        tile_dict = self.parent.parent.getInvisibleTiles()
        for invisible_tile in tile_dict:
            if self.old_tile_dict == None or tile_dict[
                    invisible_tile] != self.old_tile_dict[invisible_tile]:
                if tile_dict[invisible_tile] == 0:
                    self.floor_tile_dict[invisible_tile].setColorScale(
                        0.3, 0.3, 0.3, 1)
                    self.markChunkDirty(invisible_tile[0], invisible_tile[1])
                else:
                    self.floor_tile_dict[invisible_tile].setColorScale(
                        1, 1, 1, 1)
                    self.markChunkDirty(invisible_tile[0], invisible_tile[1])
        self.old_tile_dict = tile_dict

    def setInvisibleWallsInThread(self):
        taskMgr.add(self.setInvisibleWalls,
                    'invis_walls',
                    extraArgs=[],
                    taskChain='thread_1')

    def setInvisibleWalls(self):
        visible_wall_list = self.parent.parent.getInvisibleWalls()
        self.processLevel(visible_wall_list)
        new_list = []
        for l in visible_wall_list:
            x, y, h = self.getWallPosition(l[0], l[1])
            new_list.append((x, y, h))
        for wall in self.wall_dict:
            # If we have key in visible_wall_dict, the wall is visible
            if wall in new_list:
                self.wall_dict[wall].setColorScale(1, 1, 1, 1)
            else:
                self.wall_dict[wall].setColorScale(0.3, 0.3, 0.3, 1)
        for wall in self.dynamic_wall_dict:
            if wall in new_list:
                self.dynamic_wall_dict[wall].setColorScale(1, 1, 1, 1)
            else:
                self.dynamic_wall_dict[wall].setColorScale(0.3, 0.3, 0.3, 1)
        self.dirty_walls = 1

    def flattenTask(self, task):
        if self.pause_flatten_task:
            return task.cont

        if self.frames_counter < self.frames_between:
            self.frames_counter += 1

        if self.dirty_walls == 1:
            np = self.node_wall_original.copyTo(NodePath())
            np.clearModelNodes()
            np.flattenStrong()
            self.node_wall_usable.removeNode()
            self.node_wall_usable = np
            self.node_wall_usable.reparentTo(self.node)
            #self.node_wall_usable.setShaderAuto()
            self.dirty_walls = 0

        if len(self.dirty_chunks) > 0:
            if self.frames_counter == self.frames_between:
                chunk_tupple = self.dirty_chunks.popleft()
                np = chunk_tupple[0].copyTo(NodePath())
                np.clearModelNodes()
                np.flattenStrong()
                self.floor_usable_np_list[chunk_tupple[1]][
                    chunk_tupple[2]].removeNode()
                self.floor_usable_np_list[chunk_tupple[1]][
                    chunk_tupple[2]] = np
                self.floor_usable_np_list[chunk_tupple[1]][
                    chunk_tupple[2]].reparentTo(self.floor_usable_np)
                self.frames_counter = 0
        return task.cont
Exemple #6
0
class CollisionChecker(object):
    """
    A fast collision checker that allows maximum 32 collision pairs
    author: weiwei
    date: 20201214osaka
    """
    def __init__(self, name="auto"):
        self.ctrav = CollisionTraverser()
        self.chan = CollisionHandlerQueue()
        self.np = NodePath(name)
        self.bitmask_list = [BitMask32(2**n) for n in range(31)]
        self._bitmask_ext = BitMask32(
            2**31)  # 31 is prepared for cd with external non-active objects
        self.all_cdelements = [
        ]  # a list of cdlnks or cdobjs for quick accessing the cd elements (cdlnks/cdobjs)

    def add_cdlnks(self, jlcobj, lnk_idlist):
        """
        The collision node of the given links will be attached to self.np, but their collision bitmask will be cleared
        When the a robot_s is treated as an obstacle by another robot_s, the IntoCollideMask of its all_cdelements will be
        set to BitMask32(2**31), so that the other robot_s can compare its active_cdelements with the all_cdelements.
        :param jlcobj:
        :param lnk_idlist:
        :return:
        author: weiwei
        date: 20201216toyonaka
        """
        for id in lnk_idlist:
            if jlcobj.lnks[id]['cdprimit_childid'] == -1:  # first time add
                cdnp = jlcobj.lnks[id]['collision_model'].copy_cdnp_to(
                    self.np, clearmask=True)
                self.ctrav.addCollider(cdnp, self.chan)
                self.all_cdelements.append(jlcobj.lnks[id])
                jlcobj.lnks[id]['cdprimit_childid'] = len(
                    self.all_cdelements) - 1
            else:
                raise ValueError("The link is already added!")

    def set_active_cdlnks(self, activelist):
        """
        The specified collision links will be used for collision detection with external obstacles
        :param activelist: essentially a from list like [jlchain.lnk0, jlchain.lnk1...]
                           the correspondent tolist will be set online in cd functions
                           TODO use all elements in self.all_cdnlks if None
        :return:
        author: weiwei
        date: 20201216toyonaka
        """
        for cdlnk in activelist:
            if cdlnk['cdprimit_childid'] == -1:
                raise ValueError(
                    "The link needs to be added to collider using the add_cdlnks function first!"
                )
            cdnp = self.np.getChild(cdlnk['cdprimit_childid'])
            cdnp.node().setFromCollideMask(self._bitmask_ext)

    def set_cdpair(self, fromlist, intolist):
        """
        The given collision pair will be used for self collision detection
        :param fromlist: [[bool, cdprimit_cache], ...]
        :param intolist: [[bool, cdprimit_cache], ...]
        :return:
        author: weiwei
        date: 20201215
        """
        if len(self.bitmask_list) == 0:
            raise ValueError("Too many collision pairs! Maximum: 29")
        allocated_bitmask = self.bitmask_list.pop()
        for cdlnk in fromlist:
            if cdlnk['cdprimit_childid'] == -1:
                raise ValueError(
                    "The link needs to be added to collider using the addjlcobj function first!"
                )
            cdnp = self.np.getChild(cdlnk['cdprimit_childid'])
            current_from_cdmask = cdnp.node().getFromCollideMask()
            new_from_cdmask = current_from_cdmask | allocated_bitmask
            cdnp.node().setFromCollideMask(new_from_cdmask)
        for cdlnk in intolist:
            if cdlnk['cdprimit_childid'] == -1:
                raise ValueError(
                    "The link needs to be added to collider using the addjlcobj function first!"
                )
            cdnp = self.np.getChild(cdlnk['cdprimit_childid'])
            current_into_cdmask = cdnp.node().getIntoCollideMask()
            new_into_cdmask = current_into_cdmask | allocated_bitmask
            cdnp.node().setIntoCollideMask(new_into_cdmask)

    def add_cdobj(self, objcm, rel_pos, rel_rotmat, into_list):
        """
        :return: cdobj_info, a dictionary that mimics a joint link; Besides that, there is an additional 'into_list'
                 key to hold into_list to easily toggle off the bitmasks.
        """
        cdobj_info = {}
        cdobj_info['collision_model'] = objcm  # for reversed lookup
        cdobj_info['gl_pos'] = objcm.get_pos()
        cdobj_info['gl_rotmat'] = objcm.get_rotmat()
        cdobj_info['rel_pos'] = rel_pos
        cdobj_info['rel_rotmat'] = rel_rotmat
        cdobj_info['into_list'] = into_list
        cdnp = objcm.copy_cdnp_to(self.np, clearmask=True)
        cdnp.node().setFromCollideMask(self._bitmask_ext)  # set active
        self.ctrav.addCollider(cdnp, self.chan)
        self.all_cdelements.append(cdobj_info)
        cdobj_info['cdprimit_childid'] = len(self.all_cdelements) - 1
        self.set_cdpair([cdobj_info], into_list)
        return cdobj_info

    def delete_cdobj(self, cdobj_info):
        """
        :param cdobj_info: an lnk-like object generated by self.add_objinhnd
        :param objcm:
        :return:
        """
        self.all_cdelements.remove(cdobj_info)
        cdnp_to_delete = self.np.getChild(cdobj_info['cdprimit_childid'])
        self.ctrav.removeCollider(cdnp_to_delete)
        this_cdmask = cdnp_to_delete.node().getFromCollideMask()
        for cdlnk in cdobj_info['into_list']:
            cdnp = self.np.getChild(cdlnk['cdprimit_childid'])
            current_into_cdmask = cdnp.node().getIntoCollideMask()
            new_into_cdmask = current_into_cdmask & ~this_cdmask
            cdnp.node().setIntoCollideMask(new_into_cdmask)
        cdnp_to_delete.detachNode()
        self.bitmask_list.append(this_cdmask)

    def is_collided(self,
                    obstacle_list=[],
                    otherrobot_list=[],
                    toggle_contact_points=False):
        """
        :param obstacle_list: staticgeometricmodel
        :param otherrobot_list:
        :return:
        """
        for cdelement in self.all_cdelements:
            pos = cdelement['gl_pos']
            rotmat = cdelement['gl_rotmat']
            cdnp = self.np.getChild(cdelement['cdprimit_childid'])
            cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat))
            # print(da.npv3mat3_to_pdmat4(pos, rotmat))
            # print("From", cdnp.node().getFromCollideMask())
            # print("Into", cdnp.node().getIntoCollideMask())
        # print("xxxx colliders xxxx")
        # for collider in self.ctrav.getColliders():
        #     print(collider.getMat())
        #     print("From", collider.node().getFromCollideMask())
        #     print("Into", collider.node().getIntoCollideMask())
        # attach obstacles
        obstacle_parent_list = []
        for obstacle in obstacle_list:
            obstacle_parent_list.append(obstacle.objpdnp.getParent())
            obstacle.objpdnp.reparentTo(self.np)
        # attach other robots
        for robot in otherrobot_list:
            for cdnp in robot.cc.np.getChildren():
                current_into_cdmask = cdnp.node().getIntoCollideMask()
                new_into_cdmask = current_into_cdmask | self._bitmask_ext
                cdnp.node().setIntoCollideMask(new_into_cdmask)
            robot.cc.np.reparentTo(self.np)
        # collision check
        self.ctrav.traverse(self.np)
        # clear obstacles
        for i, obstacle in enumerate(obstacle_list):
            obstacle.objpdnp.reparentTo(obstacle_parent_list[i])
        # clear other robots
        for robot in otherrobot_list:
            for cdnp in robot.cc.np.getChildren():
                current_into_cdmask = cdnp.node().getIntoCollideMask()
                new_into_cdmask = current_into_cdmask & ~self._bitmask_ext
                cdnp.node().setIntoCollideMask(new_into_cdmask)
            robot.cc.np.detachNode()
        if self.chan.getNumEntries() > 0:
            collision_result = True
        else:
            collision_result = False
        if toggle_contact_points:
            contact_points = [
                da.pdv3_to_npv3(cd_entry.getSurfacePoint(base.render))
                for cd_entry in self.chan.getEntries()
            ]
            return collision_result, contact_points
        else:
            return collision_result

    def show_cdprimit(self):
        """
        Copy the current nodepath to base.render to show collision states
        TODO: maintain a list to allow unshow
        :return:
        author: weiwei
        date: 20220404
        """
        # print("call show_cdprimit")
        snp_cpy = self.np.copyTo(base.render)
        for cdelement in self.all_cdelements:
            pos = cdelement['gl_pos']
            rotmat = cdelement['gl_rotmat']
            cdnp = snp_cpy.getChild(cdelement['cdprimit_childid'])
            cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat))
            cdnp.show()

    def disable(self):
        """
        clear pairs and nodepath
        :return:
        """
        for cdelement in self.all_cdelements:
            cdelement['cdprimit_childid'] = -1
        self.all_cdelements = []
        for child in self.np.getChildren():
            child.removeNode()
        self.bitmask_list = list(range(31))
Exemple #7
0
    def __init__(self,
                 items,
                 parent=None,
                 buttonThrower=None,
                 onDestroy=None,
                 font=None,
                 baselineOffset=.0,
                 scale=.05,
                 itemHeight=1.,
                 leftPad=.0,
                 separatorHeight=.5,
                 underscoreThickness=1,
                 BGColor=(0, 0, 0, .7),
                 BGBorderColor=(1, .85, .4, 1),
                 separatorColor=(1, 1, 1, 1),
                 frameColorHover=(1, .85, .4, 1),
                 frameColorPress=(0, 1, 0, 1),
                 textColorReady=(1, 1, 1, 1),
                 textColorHover=(0, 0, 0, 1),
                 textColorPress=(0, 0, 0, 1),
                 textColorDisabled=(.5, .5, .5, 1),
                 minZ=None,
                 useMouseZ=True):
        '''
      items : a collection of menu items
         Item format :
            ( 'Item text', 'path/to/image', command )
                        OR
            ( 'Item text', 'path/to/image', command, arg1,arg2,.... )
         If you don't want to use an image, pass 0.

         To create disabled item, pass 0 for the command :
            ( 'Item text', 'path/to/image', 0 )
         so, you can easily switch between enabled or disabled :
            ( 'Item text', 'path/to/image', command if commandEnabled else 0 )
                        OR
            ( 'Item text', 'path/to/image', (0,command)[commandEnabled] )

         To create submenu, pass a sequence of submenu items for the command.
         To create disabled submenu, pass an empty sequence for the command.

         To enable hotkey, insert an underscore before the character,
         e.g. hotkey of 'Item te_xt' is 'x' key.

         To add shortcut key text at the right side of the item, append it at the end of
         the item text, separated by "more than" sign, e.g. 'Item text>Ctrl-T'.

         To insert separator line, pass 0 for the whole item.


      parent : where to attach the menu, defaults to aspect2d

      buttonThrower : button thrower whose thrown events are blocked temporarily
                      when the menu is displayed. If not given, the default
                      button thrower is used

      onDestroy : user function which will be called after the menu is fully destroyed
      
      font           : text font
      baselineOffset : text's baseline Z offset

      scale       : text scale
      itemHeight  : spacing between items, defaults to 1
      leftPad     : blank space width before text
      separatorHeight : separator line height, relative to itemHeight

      underscoreThickness : underscore line thickness

      BGColor, BGBorderColor, separatorColor, frameColorHover, frameColorPress,
      textColorReady, textColorHover, textColorPress, textColorDisabled
      are some of the menu components' color

      minZ : minimum Z position to restrain menu's bottom from going offscreen (-1..1).
             If it's None, it will be set a little above the screen's bottom.
      '''
        self.parent = parent if parent else aspect2d
        self.onDestroy = onDestroy
        self.BT = buttonThrower if buttonThrower else base.buttonThrowers[
            0].node()
        self.menu = NodePath('menu-%s' % id(self))
        self.parentMenu = None
        self.submenu = None
        self.BTprefix = self.menu.getName() + '>'
        self.submenuCreationTaskName = 'createSubMenu-' + self.BTprefix
        self.submenuRemovalTaskName = 'removeSubMenu-' + self.BTprefix
        self.font = font if font else TextNode.getDefaultFont()
        self.baselineOffset = baselineOffset
        if isinstance(scale, (float, int)):
            scale = (scale, 1.0, scale)
        self.scale = scale
        self.itemHeight = itemHeight
        self.leftPad = leftPad
        self.separatorHeight = separatorHeight
        self.underscoreThickness = underscoreThickness
        self.BGColor = BGColor
        self.BGBorderColor = BGBorderColor
        self.separatorColor = separatorColor
        self.frameColorHover = frameColorHover
        self.frameColorPress = frameColorPress
        self.textColorReady = textColorReady
        self.textColorHover = textColorHover
        self.textColorPress = textColorPress
        self.textColorDisabled = textColorDisabled
        self.minZ = minZ
        self.mpos = Point2(base.mouseWatcherNode.getMouse())

        self.itemCommand = []
        self.hotkeys = {}
        self.numItems = 0
        self.sel = -1
        self.selByKey = False

        bgPad = self.bgPad = .0125
        texMargin = self.font.getTextureMargin() * self.scale[0] * .25
        b = DirectButton(parent=NodePath(''),
                         text='^|g_',
                         text_font=self.font,
                         scale=self.scale)
        fr = b.node().getFrame()
        b.getParent().removeNode()
        baselineToCenter = (fr[2] + fr[3]) * self.scale[0]
        LH = (fr[3] - fr[2]) * self.itemHeight * self.scale[2]
        imageHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .85
        arrowHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .5
        baselineToTop = (fr[3] * self.itemHeight * self.scale[2] /
                         LH) / (1. + self.baselineOffset)
        baselineToBot = LH / self.scale[2] - baselineToTop
        itemZcenter = (baselineToTop - baselineToBot) * .5
        separatorHalfHeight = .5 * separatorHeight * LH
        LSseparator = LineSegs()
        LSseparator.setColor(.5, .5, .5, .2)

        arrowVtx = [
            (0, itemZcenter),
            (-2 * arrowHalfHeight, itemZcenter + arrowHalfHeight),
            (-arrowHalfHeight, itemZcenter),
            (-2 * arrowHalfHeight, itemZcenter - arrowHalfHeight),
        ]
        tri = Triangulator()
        vdata = GeomVertexData('trig', GeomVertexFormat.getV3(), Geom.UHStatic)
        vwriter = GeomVertexWriter(vdata, 'vertex')
        for x, z in arrowVtx:
            vi = tri.addVertex(x, z)
            vwriter.addData3f(x, 0, z)
            tri.addPolygonVertex(vi)
        tri.triangulate()
        prim = GeomTriangles(Geom.UHStatic)
        for i in range(tri.getNumTriangles()):
            prim.addVertices(tri.getTriangleV0(i), tri.getTriangleV1(i),
                             tri.getTriangleV2(i))
            prim.closePrimitive()
        geom = Geom(vdata)
        geom.addPrimitive(prim)
        geomNode = GeomNode('arrow')
        geomNode.addGeom(geom)
        realArrow = NodePath(geomNode)
        z = -baselineToTop * self.scale[2] - bgPad
        maxWidth = .1 / self.scale[0]
        shortcutTextMaxWidth = 0
        anyImage = False
        anyArrow = False
        anyShortcut = False
        arrows = []
        shortcutTexts = []
        loadPrcFileData('', 'text-flatten 0')
        for item in items:
            if item:
                t, imgPath, f = item[:3]
                haveSubmenu = type(f) in SEQUENCE_TYPES
                anyArrow |= haveSubmenu
                anyImage |= isinstance(imgPath, bool) or bool(imgPath)
                disabled = not len(f) if haveSubmenu else not callable(f)
                args = item[3:]
                underlinePos = t.find('_')
                t = t.replace('_', '')
                shortcutSepPos = t.find('>')
                if shortcutSepPos > -1:
                    if haveSubmenu:
                        print(
                            "\nA SHORTCUT KEY POINTING TO A SUBMENU IS NON-SENSE, DON'T YOU AGREE ?"
                        )
                    else:
                        shortcutText = NodePath(
                            OnscreenText(
                                parent=self.menu,
                                text=t[shortcutSepPos + 1:],
                                font=self.font,
                                scale=1,
                                fg=(1, 1, 1, 1),
                                align=TextNode.ARight,
                            ))
                        shortcutTextMaxWidth = max(
                            shortcutTextMaxWidth,
                            abs(shortcutText.getTightBounds()[0][0]))
                        anyShortcut = True
                    t = t[:shortcutSepPos]
                else:
                    shortcutText = ''
                EoLcount = t.count('\n')
                arrowZpos = -self.font.getLineHeight() * EoLcount * .5
                if disabled:
                    b = NodePath(
                        OnscreenText(
                            parent=self.menu,
                            text=t,
                            font=self.font,
                            scale=1,
                            fg=textColorDisabled,
                            align=TextNode.ALeft,
                        ))
                    # don't pass the scale and position to OnscreenText constructor,
                    # to maintain correctness between the OnscreenText and DirectButton items
                    # due to the new text generation implementation
                    b.setScale(self.scale)
                    b.setZ(z)
                    maxWidth = max(maxWidth,
                                   b.getTightBounds()[1][0] / self.scale[0])
                    if shortcutText:
                        shortcutText.reparentTo(b)
                        shortcutText.setColor(Vec4(*textColorDisabled), 1)
                        shortcutText.setZ(arrowZpos)
                        shortcutTexts.append(shortcutText)
                else:
                    b = DirectButton(
                        parent=self.menu,
                        text=t,
                        text_font=self.font,
                        scale=self.scale,
                        pos=(0, 0, z),
                        text_fg=textColorReady,
                        # text color when mouse over
                        text2_fg=textColorHover,
                        # text color when pressed
                        text1_fg=textColorHover
                        if haveSubmenu else textColorPress,
                        # framecolor when pressed
                        frameColor=frameColorHover
                        if haveSubmenu else frameColorPress,
                        commandButtons=[DGG.LMB, DGG.RMB],
                        command=(lambda: 0)
                        if haveSubmenu else self.__runCommand,
                        extraArgs=[] if haveSubmenu else [f, args],
                        text_align=TextNode.ALeft,
                        relief=DGG.FLAT,
                        rolloverSound=0,
                        clickSound=0,
                        pressEffect=0)
                    b.stateNodePath[2].setColor(
                        *frameColorHover)  # framecolor when mouse over
                    b.stateNodePath[0].setColor(0, 0, 0,
                                                0)  # framecolor when ready
                    bframe = Vec4(b.node().getFrame())
                    if EoLcount:
                        bframe.setZ(EoLcount * 10)
                        b['frameSize'] = bframe
                    maxWidth = max(maxWidth, bframe[1])
                    if shortcutText:
                        for snpi, col in ((0, textColorReady),
                                          (1, textColorPress),
                                          (2, textColorHover)):
                            sct = shortcutText.copyTo(b.stateNodePath[snpi],
                                                      sort=10)
                            sct.setColor(Vec4(*col), 1)
                            sct.setZ(arrowZpos)
                            shortcutTexts.append(sct)
                        shortcutText.removeNode()
                if isinstance(imgPath, bool):
                    if imgPath:
                        if disabled:
                            fg = textColorDisabled
                        else:
                            fg = textColorReady
                        tick = NodePath(
                            OnscreenText(
                                parent=b,
                                text=u"\u2714",
                                font=self.font,
                                scale=1,
                                fg=fg,
                                align=TextNode.ALeft,
                            ))
                        tick.setX(-2 * imageHalfHeight - leftPad)
                elif imgPath:
                    img = loader.loadTexture(imgPath, okMissing=True)
                    if img is not None:
                        if disabled:
                            if imgPath in PopupMenu.grayImages:
                                img = PopupMenu.grayImages[imgPath]
                            else:
                                pnm = PNMImage()
                                img.store(pnm)
                                pnm.makeGrayscale(.2, .2, .2)
                                img = Texture()
                                img.load(pnm)
                                PopupMenu.grayImages[imgPath] = img
                        img.setMinfilter(Texture.FTLinearMipmapLinear)
                        img.setWrapU(Texture.WMClamp)
                        img.setWrapV(Texture.WMClamp)
                        CM = CardMaker('')
                        CM.setFrame(-2 * imageHalfHeight - leftPad, -leftPad,
                                    itemZcenter - imageHalfHeight,
                                    itemZcenter + imageHalfHeight)
                        imgCard = b.attachNewNode(CM.generate())
                        imgCard.setTexture(img)
                if underlinePos > -1:
                    oneLineText = t[:underlinePos + 1]
                    oneLineText = oneLineText[oneLineText.rfind('\n') + 1:]
                    tn = TextNode('')
                    tn.setFont(self.font)
                    tn.setText(oneLineText)
                    tnp = NodePath(tn.getInternalGeom())
                    underlineXend = tnp.getTightBounds()[1][0]
                    tnp.removeNode()
                    tn.setText(t[underlinePos])
                    tnp = NodePath(tn.getInternalGeom())
                    b3 = tnp.getTightBounds()
                    underlineXstart = underlineXend - (b3[1] - b3[0])[0]
                    tnp.removeNode()
                    underlineZpos = -.7 * baselineToBot - self.font.getLineHeight(
                    ) * t[:underlinePos].count('\n')
                    LSunder = LineSegs()
                    LSunder.setThickness(underscoreThickness)
                    LSunder.moveTo(underlineXstart + texMargin, 0,
                                   underlineZpos)
                    LSunder.drawTo(underlineXend - texMargin, 0, underlineZpos)
                    if disabled:
                        underline = b.attachNewNode(LSunder.create())
                        underline.setColor(Vec4(*textColorDisabled), 1)
                    else:
                        underline = b.stateNodePath[0].attachNewNode(
                            LSunder.create())
                        underline.setColor(Vec4(*textColorReady), 1)
                        underline.copyTo(b.stateNodePath[1], 10).setColor(
                            Vec4(*textColorHover
                                 if haveSubmenu else textColorPress), 1)
                        underline.copyTo(b.stateNodePath[2],
                                         10).setColor(Vec4(*textColorHover), 1)
                        hotkey = t[underlinePos].lower()
                        if hotkey in self.hotkeys:
                            self.hotkeys[hotkey].append(self.numItems)
                        else:
                            self.hotkeys[hotkey] = [self.numItems]
                            self.accept(self.BTprefix + hotkey,
                                        self.__processHotkey, [hotkey])
                            self.accept(self.BTprefix + 'alt-' + hotkey,
                                        self.__processHotkey, [hotkey])
                if haveSubmenu:
                    if disabled:
                        arrow = realArrow.instanceUnderNode(b, '')
                        arrow.setColor(Vec4(*textColorDisabled), 1)
                        arrow.setZ(arrowZpos)
                    else:
                        arrow = realArrow.instanceUnderNode(
                            b.stateNodePath[0], 'r')
                        arrow.setColor(Vec4(*textColorReady), 1)
                        arrow.setZ(arrowZpos)
                        arrPress = realArrow.instanceUnderNode(
                            b.stateNodePath[1], 'p')
                        arrPress.setColor(Vec4(*textColorHover), 1)
                        arrPress.setZ(arrowZpos)
                        arrHover = realArrow.instanceUnderNode(
                            b.stateNodePath[2], 'h')
                        arrHover.setColor(Vec4(*textColorHover), 1)
                        arrHover.setZ(arrowZpos)
                        # weird, if sort order is 0, it's obscured by the frame
                        for a in (arrPress, arrHover):
                            a.reparentTo(a.getParent(), sort=10)
                if not disabled:
                    extraArgs = [self.numItems, f if haveSubmenu else 0]
                    self.accept(DGG.ENTER + b.guiId, self.__hoverOnItem,
                                extraArgs)
                    self.accept(DGG.EXIT + b.guiId, self.__offItem)
                    #~ self.itemCommand.append((None,0) if haveSubmenu else (f,args))
                    self.itemCommand.append((f, args))
                    if self.numItems == 0:
                        self.firstButtonIdx = int(b.guiId[2:])
                    self.numItems += 1
                z -= LH + self.font.getLineHeight() * self.scale[2] * EoLcount
            else:  # SEPARATOR LINE
                z += LH - separatorHalfHeight - baselineToBot * self.scale[2]
                LSseparator.moveTo(0, 0, z)
                LSseparator.drawTo(self.scale[0] * .5, 0, z)
                LSseparator.drawTo(self.scale[0], 0, z)
                z -= separatorHalfHeight + baselineToTop * self.scale[2]
        maxWidth += 7 * arrowHalfHeight * (
            anyArrow or anyShortcut) + .2 + shortcutTextMaxWidth
        arrowXpos = maxWidth - arrowHalfHeight
        realArrow.setX(arrowXpos)
        if anyImage:
            leftPad += 2 * imageHalfHeight + leftPad
        for sct in shortcutTexts:
            sct.setX(maxWidth - 2 * (arrowHalfHeight * anyArrow + .2))
        for c in asList(self.menu.findAllMatches('**/DirectButton*')):
            numLines = c.node().getFrame()[2]
            c.node().setFrame(
                Vec4(
                    -leftPad, maxWidth, -baselineToBot -
                    (numLines * .1 * self.itemHeight if numLines >= 10 else 0),
                    baselineToTop))
        loadPrcFileData('', 'text-flatten 1')

        try:
            minZ = self.menu.getChild(0).getRelativePoint(
                b, Point3(0, 0,
                          b.node().getFrame()[2]))[2]
        except:
            minZ = self.menu.getChild(0).getRelativePoint(
                self.menu, Point3(
                    0, 0,
                    b.getTightBounds()[0][2]))[2] - baselineToBot * .5
        try:
            top = self.menu.getChild(0).node().getFrame()[3]
        except:
            top = self.menu.getChild(0).getZ() + baselineToTop
        l, r, b, t = -leftPad - bgPad / self.scale[
            0], maxWidth + bgPad / self.scale[0], minZ - bgPad / self.scale[
                2], top + bgPad / self.scale[2]
        menuBG = DirectFrame(parent=self.menu.getChild(0),
                             frameSize=(l, r, b, t),
                             frameColor=BGColor,
                             state=DGG.NORMAL,
                             suppressMouse=1)
        menuBorder = self.menu.getChild(0).attachNewNode('border')
        borderVtx = (
            (l, 0, b),
            (l, 0, .5 * (b + t)),
            (l, 0, t),
            (.5 * (l + r), 0, t),
            (r, 0, t),
            (r, 0, .5 * (b + t)),
            (r, 0, b),
            (.5 * (l + r), 0, b),
            (l, 0, b),
        )
        LSborderBG = LineSegs()
        LSborderBG.setThickness(4)
        LSborderBG.setColor(0, 0, 0, .7)
        LSborderBG.moveTo(*(borderVtx[0]))
        for v in borderVtx[1:]:
            LSborderBG.drawTo(*v)
        # fills the gap at corners
        for v in range(0, 7, 2):
            LSborderBG.moveTo(*(borderVtx[v]))
        menuBorder.attachNewNode(LSborderBG.create())
        LSborder = LineSegs()
        LSborder.setThickness(2)
        LSborder.setColor(*BGBorderColor)
        LSborder.moveTo(*(borderVtx[0]))
        for v in borderVtx[1:]:
            LSborder.drawTo(*v)
        menuBorder.attachNewNode(LSborder.create())
        for v in range(1, 8, 2):
            LSborderBG.setVertexColor(v, Vec4(0, 0, 0, .1))
            LSborder.setVertexColor(v, Vec4(.3, .3, .3, .5))
        menuBorderB3 = menuBorder.getTightBounds()
        menuBorderDims = menuBorderB3[1] - menuBorderB3[0]
        menuBG.wrtReparentTo(self.menu, sort=-1)
        self.menu.reparentTo(self.parent)
        x = -menuBorderB3[0][0] * self.scale[0]
        for c in asList(self.menu.getChildren()):
            c.setX(x)
        self.maxWidth = maxWidth = menuBorderDims[0]
        self.height = menuBorderDims[2]
        maxWidthR2D = maxWidth * self.menu.getChild(0).getSx(render2d)
        separatorLines = self.menu.attachNewNode(LSseparator.create(), 10)
        separatorLines.setSx(maxWidth)
        for v in range(1, LSseparator.getNumVertices(), 3):
            LSseparator.setVertexColor(v, Vec4(*separatorColor))
        x = clamp(-.98, .98 - maxWidthR2D, self.mpos[0] - maxWidthR2D * .5)
        minZ = (-.98 if self.minZ is None else self.minZ)
        z = clamp(
            minZ +
            menuBorderDims[2] * self.scale[2] * self.parent.getSz(render2d),
            .98, self.mpos[1] if useMouseZ else -1000)
        self.menu.setPos(render2d, x, 0, z)
        self.menu.setTransparency(1)

        self.origBTprefix = self.BT.getPrefix()
        self.BT.setPrefix(self.BTprefix)
        self.accept(self.BTprefix + 'escape', self.destroy)
        for e in ('mouse1', 'mouse3'):
            self.accept(self.BTprefix + e, self.destroy, [True])
        self.accept(self.BTprefix + 'arrow_down', self.__nextItem)
        self.accept(self.BTprefix + 'arrow_down-repeat', self.__nextItem)
        self.accept(self.BTprefix + 'arrow_up', self.__prevItem)
        self.accept(self.BTprefix + 'arrow_up-repeat', self.__prevItem)
        self.accept(self.BTprefix + 'enter', self.__runSelItemCommand)
        self.accept(self.BTprefix + 'space', self.__runSelItemCommand)