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()
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()
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
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
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
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))
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)