def _updateDebugNode(self): """ Internal method to generate new debug geometry. """ mainNode = NodePath("DebugNodeInner") mainNode.setPos(self.position) lineNode = mainNode.attachNewNode("lines") inner = Globals.loader.loadModel("box") inner.setPos(-0.5, -0.5, 0.6) inner.flattenStrong() inner.reparentTo(mainNode) # Generate outer circles points1 = [] points2 = [] points3 = [] for i in range(self.visualizationNumSteps + 1): angle = float(i) / float( self.visualizationNumSteps) * math.pi * 2.0 points1.append(Vec3(0, math.sin(angle), math.cos(angle))) points2.append(Vec3(math.sin(angle), math.cos(angle), 0)) points3.append(Vec3(math.sin(angle), 0, math.cos(angle))) self._createDebugLine(points1, False).reparentTo(lineNode) self._createDebugLine(points2, False).reparentTo(lineNode) self._createDebugLine(points3, False).reparentTo(lineNode) lineNode.setScale(self.radius) mainNode.flattenStrong() self.debugNode.node().removeAllChildren() mainNode.reparentTo(self.debugNode)
def makeSprite(name, texture, scale, add = False): from panda3d.core import (GeomVertexFormat, GeomVertexData, GeomEnums, InternalName, GeomVertexWriter, GeomPoints, Geom, GeomNode, NodePath, TextureStage, TexGenAttrib, BoundingSphere) format = GeomVertexFormat.getV3() data = GeomVertexData(name + "_data", format, GeomEnums.UHStatic) writer = GeomVertexWriter(data, InternalName.getVertex()) writer.addData3f((0, 0, 0)) primitive = GeomPoints(GeomEnums.UHStatic) primitive.addVertex(0) primitive.closePrimitive() geom = Geom(data) geom.addPrimitive(primitive) geomNode = GeomNode(name) geomNode.addGeom(geom) np = NodePath(geomNode) np.setLightOff(1) np.setMaterialOff(1) np.setRenderModePerspective(True) ts = TextureStage('sprite') if add: ts.setMode(TextureStage.MAdd) np.setTexture(ts, texture) np.setTexGen(ts, TexGenAttrib.MPointSprite) np.setDepthWrite(False) np.setDepthOffset(1) np.setTransparency(True) np.node().setBounds(BoundingSphere((0, 0, 0), 1)) np.node().setFinal(True) np.flattenStrong() np.setScale(scale) return np
def getStatic(self): """ this makes a flattened version of the tree for faster rendering... """ np = NodePath(self.node().copySubgraph()) np.flattenStrong() return np
def _updateDebugNode(self): """ Internal method to generate new debug geometry. """ mainNode = NodePath("DebugNodeInner") mainNode.setPos(self.position) lineNode = mainNode.attachNewNode("lines") inner = Globals.loader.loadModel("box") inner.setPos(-0.5, -0.5, 0.6) inner.flattenStrong() inner.reparentTo(mainNode) # Generate outer circles points1 = [] points2 = [] points3 = [] for i in range(self.visualizationNumSteps + 1): angle = float( i) / float(self.visualizationNumSteps) * math.pi * 2.0 points1.append(Vec3(0, math.sin(angle), math.cos(angle))) points2.append(Vec3(math.sin(angle), math.cos(angle), 0)) points3.append(Vec3(math.sin(angle), 0, math.cos(angle))) self._createDebugLine(points1, False).reparentTo(lineNode) self._createDebugLine(points2, False).reparentTo(lineNode) self._createDebugLine(points3, False).reparentTo(lineNode) lineNode.setScale(self.radius) mainNode.flattenStrong() self.debugNode.node().removeAllChildren() mainNode.reparentTo(self.debugNode)
def changeModel(newmodel): tempParent=NodePath('') newModel=loader.loadModel(newmodel) newModel.findAllMatches('**/+GeomNode').reparentTo(tempParent) tempParent.setScale(1.5/tempParent.getBounds().getRadius()) tempParent.setPos(-tempParent.getBounds().getCenter()) tempParent.flattenStrong() tempParent.find('**/+GeomNode').node().replaceNode(model.node())
def DisplacementUVSphere(radius, heightmap, scale, rings=5, sectors=5, inv_texture_u=False, inv_texture_v=True): data = EggData() pool = EggVertexPool('pool') vertices = [] data.addChild(pool) R = 1. / (rings) S = 1. / (sectors) for r in range(0, rings + 1): for s in range(0, sectors + 1): cos_s = cos(2 * pi * s * S + pi) sin_s = sin(2 * pi * s * S + pi) sin_r = sin(pi * r * R) cos_r = cos(pi * r * R) x = cos_s * sin_r y = sin_s * sin_r z = cos_r vertex = EggVertex() u = s * S v = r * R height = radius + heightmap.get_height_uv(u, v) * scale vertex.setPos(LPoint3d(x * height, y * height, z * height)) if inv_texture_v: v = 1.0 - v if inv_texture_u: u = 1.0 - u vertex.setUv(LPoint2d(u, v)) pool.addVertex(vertex) vertices.append(vertex) index = 0 for r in range(0, rings): for s in range(0, sectors): poly = EggPolygon() data.addChild(poly) poly.addVertex(vertices[index + sectors + 1]) poly.addVertex(vertices[index]) poly.addVertex(vertices[index + sectors]) poly = EggPolygon() data.addChild(poly) poly.addVertex(vertices[index + sectors + 1]) poly.addVertex(vertices[index + 1]) poly.addVertex(vertices[index]) index += 1 data.removeUnusedVertices(True) data.recomputeVertexNormals(45) data.recomputeTangentBinormal(GlobPattern("")) node = loadEggData(data) path = NodePath(node) path.flattenStrong() return path
def addToon(self, toon, tX, tY): marker = NodePath('toon_marker-%i' % toon.doId) marker.reparentTo(self) self._getToonMarker(toon).copyTo(marker) marker.setColor(toon.style.getHeadColor()) if toon.isLocal(): marker.setScale(0.07) else: marker.setScale(0.05) marker.flattenStrong() marker.setPos(*self.gui2pos(*self.tile2gui(tX, tY))) self._toon2marker[toon] = marker
def addToon(self, toon): marker = NodePath('toon_marker-%i' % toon.doId) marker.reparentTo(self) self._getToonMarker(toon).copyTo(marker) marker.setColor(toon.style.getHeadColor()) if toon.isLocal(): marker.setScale(Globals.Gui.LocalMarkerScale) marker.setBin('fixed', 10) else: marker.setScale(Globals.Gui.MarkerScale) marker.setBin('fixed', 5) marker.flattenStrong() self._toonMarkers[toon] = marker
class SkyBox(object): def __init__(self, modelPath, scale=1.0): self._nodePath = NodePath('skybox') self._nodePath.setPos((0, 0, 0)) self._scale = scale m = base.assetManager.loadModel(modelPath) m.reparentTo(self._nodePath) self._nodePath.setScale(self._scale) self._nodePath.flattenStrong() self._nodePath.setLightOff(True) self._nodePath.setShaderOff() self._nodePath.setDepthWrite(False) @property def nodePath(self): return self._nodePath
class SkyBox(object): def __init__(self, modelPath, scale=1.0): self._nodePath = NodePath('skybox') self._nodePath.setPos((0,0,0)) self._scale = scale m = base.assetManager.loadModel(modelPath) m.reparentTo(self._nodePath) self._nodePath.setScale(self._scale) self._nodePath.flattenStrong() self._nodePath.setLightOff(True) self._nodePath.setShaderOff() self._nodePath.setDepthWrite(False) @property def nodePath(self): return self._nodePath
class GuiCard: def __init__(self, width, height, border_size, pos, hugpos, color): self.hugpos = hugpos self.width = width self.height = height self.border_size = border_size self.pos = pos self.node = NodePath("guicard") self.frame_node = NodePath("frame") cm = CardMaker("cm_left") cm.setFrame(0, width, 0, height) n = self.frame_node.attachNewNode(cm.generate()) n.setPos(0, 0, 0) self.frame_node.setColor(color) self.frame_node.flattenStrong() self.frame_node.reparentTo(self.node) self.frame_node.setTransparency(1) self.node.reparentTo(aspect2d)#@UndefinedVariable self.redraw() def setTexture(self, tex): self.frame_node.setTexture(tex) def redraw(self): if self.hugpos == "topleft": p = base.a2dTopLeft.getPos()#@UndefinedVariable p.setZ(p.getZ() - self.height) elif self.hugpos == "topright": p = base.a2dTopRight.getPos()#@UndefinedVariable p.setZ(p.getZ() - self.height) elif self.hugpos == "bottomleft": p = base.a2dBottomLeft.getPos()#@UndefinedVariable p.setX(p.getX() + 0.107) p.setZ(p.getZ()) elif self.hugpos == None: p = self.pos self.node.setPos(p) def removeNode(self): self.node.removeNode()
def makeChunkNode(chunk, texture): """ Function that creates a NodePath containing a chunk from a block array chunk: Block array texture: Texture map to use """ geom = GeomNode("chunk") for x in range(16): for y in range(16): for z in range(16): btype = chunk[x][y][z] if not btype in Blocks: btype = 2 # Unknown if Blocks[btype]["visible"]: makeCube(geom, float(x), float(y), float(z), texpos=Blocks[btype]["texcoords"]) chunk = NodePath(geom) chunk.setTexture(texture) chunk.flattenStrong() return chunk
def flatten_animated_tiles(self, group_node): # FIXME: hard to read: get_child() everywhere # Makes a new node for each frame using all its tiles # flatten the s*** out of the node and add to a new SequenceNode. tiles = group_node.get_children() flattened_sequence = SequenceNode(tiles[0].name) for a, animation in enumerate(tiles[0].node().get_children()): for f, frame in enumerate(animation.get_child(0).get_children()): combined_frame = NodePath("frame " + str(f)) for tile in tiles: new_np = NodePath("frame") new_np.set_pos(tile.get_pos()) animation = tile.node().get_child(a).get_child(0) new_np.attach_new_node(animation.get_child(f)) new_np.reparent_to(combined_frame) combined_frame.flattenStrong() flattened_sequence.add_child(combined_frame.node()) framerate = animation.get_frame_rate() flattened_sequence.set_frame_rate(framerate) flattened_sequence.loop(True) return NodePath(flattened_sequence)
def getBam(mesh, filename): scene_members = pandacore.getSceneMembers(mesh) rotateNode = GeomNode("rotater") rotatePath = NodePath(rotateNode) matrix = numpy.identity(4) if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP: r = collada.scene.RotateTransform(0,1,0,90) matrix = r.matrix elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP: r = collada.scene.RotateTransform(1,0,0,90) matrix = r.matrix rotatePath.setMat(Mat4(*matrix.T.flatten().tolist())) for geom, renderstate, mat4 in scene_members: node = GeomNode("primitive") node.addGeom(geom) if renderstate is not None: node.setGeomState(0, renderstate) geomPath = rotatePath.attachNewNode(node) geomPath.setMat(mat4) rotatePath.flattenStrong() wrappedNode = pandacore.centerAndScale(rotatePath) model_name = filename.replace('/', '_') wrappedNode.setName(model_name) bam_temp = tempfile.mktemp(suffix = model_name + '.bam') wrappedNode.writeBamFile(bam_temp) bam_f = open(bam_temp, 'rb') bam_data = bam_f.read() bam_f.close() os.remove(bam_temp) return bam_data
def flattenReallyStrong(n): """ force the passed nodepath into a single geomNode, then flattens the geomNode to minimize geomCount. In many cases, flattenStrong is not enough, and for good reason. This code ignores all potential issues and forces it into one node. It will alwayse do so. RenderStates are preserved, transformes are applied, but tags, special node classes and any other such data is all lost. This modifies the passed NodePath as a side effect and returns the new NodePath. """ # make sure all the transforms are applied to the geoms n.flattenLight() # make GeomNode to store results in. g=GeomNode("flat_"+n.getName()) g.setState(n.getState()) # a little helper to process GeomNodes since we need it in 2 places def f(c): rs=c.getState(n) gg=c.node() for i in xrange(gg.getNumGeoms()): g.addGeom(gg.modifyGeom(i),rs.compose(gg.getGeomState(i))) # special case this node being a GeomNode so we don't skips its geoms. if n.node().isGeomNode(): f(n) # proccess all GeomNodes for c in n.findAllMatches('**/+GeomNode'): f(c) nn=NodePath(g) nn.setMat(n.getMat()) # merge geoms nn.flattenStrong() return nn
def _updateDebugNode(self): # self.debug("updating debug node") mainNode = NodePath("DebugNodeInner") mainNode.setPos(self.position) # inner = loader.loadModel("Assets/Visualisation/Lamp") # inner.setPos(-0.5, -0.5, 0.0) # inner.setScale(0.5) # inner.setColorScale(Vec4(1,1,0,1)) # inner.reparentTo(mainNode) lineNode = mainNode.attachNewNode("lines") # Outer circles points1 = [] points2 = [] points3 = [] for i in xrange(self.visualizationNumSteps + 1): angle = float(i) / float( self.visualizationNumSteps) * math.pi * 2.0 points1.append(Vec3(0, math.sin(angle), math.cos(angle))) points2.append(Vec3(math.sin(angle), math.cos(angle), 0)) points3.append(Vec3(math.sin(angle), 0, math.cos(angle))) self._createDebugLine(points1, False).reparentTo(lineNode) self._createDebugLine(points2, False).reparentTo(lineNode) self._createDebugLine(points3, False).reparentTo(lineNode) lineNode.setScale(self.radius) # mainNode.setHpr(self.rotation) mainNode.flattenStrong() self.debugNode.node().removeAllChildren() mainNode.reparentTo(self.debugNode)
class Block(Element, BlockDefault): """ Abstract class of Block, BlockSocket: a part of previous block connecting this block <---------------------------------------------- road_2_end <---------------------- road_2_start <~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~> road_1_start ----------------------> road_1_end ----------------------------------------------> BlockSocket = tuple(road_1, road_2) When single-direction block created, road_2 in block socket is useless. But it's helpful when a town is created. """ def __init__(self, block_index: int, pre_block_socket: BlockSocket, global_network: RoadNetwork, random_seed): super(Block, self).__init__(random_seed) # block information assert self.ID is not None, "Each Block must has its unique ID When define Block" assert len(self.ID) == 1, "Block ID must be a character " assert self.SOCKET_NUM is not None, "The number of Socket should be specified when define a new block" if block_index == 0: from pgdrive.scene_creator.blocks import FirstBlock assert isinstance( self, FirstBlock), "only first block can use block index 0" elif block_index < 0: logging.debug("It is recommended that block index should > 1") self._block_name = str(block_index) + self.ID self.block_index = block_index self.number_of_sample_trial = 0 # each block contains its own road network and a global network self._global_network = global_network self.block_network = RoadNetwork() # used to spawn npc self._respawn_roads = [] # own sockets, one block derives from a socket, but will have more sockets to connect other blocks self._sockets = OrderedDict() # used to connect previous blocks, save its info here self.pre_block_socket = pre_block_socket self.pre_block_socket_index = pre_block_socket.index # a bounding box used to improve efficiency x_min, x_max, y_min, y_max self.bounding_box = None # used to create this block, but for first block it is nonsense if block_index != 0: self.positive_lanes = self.pre_block_socket.positive_road.get_lanes( self._global_network) self.negative_lanes = self.pre_block_socket.negative_road.get_lanes( self._global_network) self.positive_lane_num = len(self.positive_lanes) self.negative_lane_num = len(self.negative_lanes) self.positive_basic_lane = self.positive_lanes[ -1] # most right or outside lane is the basic lane self.negative_basic_lane = self.negative_lanes[ -1] # most right or outside lane is the basic lane self.lane_width = self.positive_basic_lane.width_at(0) if self.render: # render pre-load self.road_texture = self.loader.loadTexture( AssetLoader.file_path("textures", "sci", "color.jpg")) self.road_texture.setMinfilter( SamplerState.FT_linear_mipmap_linear) self.road_texture.setAnisotropicDegree(8) self.road_normal = self.loader.loadTexture( AssetLoader.file_path("textures", "sci", "normal.jpg")) self.ts_color = TextureStage("color") self.ts_normal = TextureStage("normal") self.side_texture = self.loader.loadTexture( AssetLoader.file_path("textures", "sidewalk", "color.png")) self.side_texture.setMinfilter( SamplerState.FT_linear_mipmap_linear) self.side_texture.setAnisotropicDegree(8) self.side_normal = self.loader.loadTexture( AssetLoader.file_path("textures", "sidewalk", "normal.png")) self.sidewalk = self.loader.loadModel( AssetLoader.file_path("models", "box.bam")) def construct_block(self, root_render_np: NodePath, pg_physics_world: PGPhysicsWorld, extra_config: Dict = None) -> bool: """ Randomly Construct a block, if overlap return False """ self.set_config(self.PARAMETER_SPACE.sample()) if extra_config: self.set_config(extra_config) success = self._sample_topology() self._create_in_world() self.attach_to_pg_world(root_render_np, pg_physics_world) return success def destruct_block(self, pg_physics_world: PGPhysicsWorld): self._clear_topology() self.detach_from_pg_world(pg_physics_world) self.node_path.removeNode() self.dynamic_nodes.clear() self.static_nodes.clear() def _sample_topology(self) -> bool: """ Sample a new topology, clear the previous settings at first """ self.number_of_sample_trial += 1 self._clear_topology() no_cross = self._try_plug_into_previous_block() self._global_network.add(self.block_network) return no_cross def construct_from_config(self, config: Dict, root_render_np: NodePath, pg_physics_world: PGPhysicsWorld): assert set(config.keys()) == self.PARAMETER_SPACE.parameters, \ "Make sure the parameters' name are as same as what defined in pg_space.py" self.set_config(config) success = self._sample_topology() self._create_in_world() self.attach_to_pg_world(root_render_np, pg_physics_world) return success def get_socket(self, index: Union[str, int]) -> BlockSocket: if isinstance(index, int): if index < 0 or index >= len(self._sockets): raise ValueError("Socket of {}: index out of range".format( self.class_name)) socket_index = list(self._sockets)[index] else: assert index.startswith(self._block_name) socket_index = index assert socket_index in self._sockets, (socket_index, self._sockets.keys()) return self._sockets[socket_index] def add_respawn_roads(self, respawn_roads: Union[List[Road], Road]): """ Use this to add spawn roads instead of modifying the list directly """ if isinstance(respawn_roads, List): for road in respawn_roads: self._add_one_respawn_road(road) elif isinstance(respawn_roads, Road): self._add_one_respawn_road(respawn_roads) else: raise ValueError("Only accept List[Road] or Road in this func") def get_respawn_roads(self): return self._respawn_roads def get_respawn_lanes(self): """ return a 2-dim array [[]] to keep the lane index """ ret = [] for road in self._respawn_roads: lanes = road.get_lanes(self.block_network) ret.append(lanes) return ret def add_sockets(self, sockets: Union[List[BlockSocket], BlockSocket]): """ Use this to add sockets instead of modifying the list directly """ if isinstance(sockets, BlockSocket): self._add_one_socket(sockets) elif isinstance(sockets, List): for socket in sockets: self._add_one_socket(socket) def set_part_idx(self, x): """ It is necessary to divide block to some parts in complex block and give them unique id according to part idx """ self.PART_IDX = x self.ROAD_IDX = 0 # clear the road idx when create new part def add_road_node(self): """ Call me to get a new node name of this block. It is more accurate and recommended to use road_node() to get a node name """ self.ROAD_IDX += 1 return self.road_node(self.PART_IDX, self.ROAD_IDX - 1) def road_node(self, part_idx: int, road_idx: int) -> str: """ return standard road node name """ return self.node(self.block_index, part_idx, road_idx) @classmethod def node(cls, block_idx: int, part_idx: int, road_idx: int) -> str: return str(block_idx) + cls.ID + str(part_idx) + cls.DASH + str( road_idx) + cls.DASH def _add_one_socket(self, socket: BlockSocket): assert isinstance( socket, BlockSocket), "Socket list only accept BlockSocket Type" if socket.index is not None and not socket.index.startswith( self._block_name): logging.warning( "The adding socket has index {}, which is not started with this block name {}. This is dangerous! " "Current block has sockets: {}.".format( socket.index, self._block_name, self.get_socket_indices())) if socket.index is None: # if this socket is self block socket socket.set_index(self._block_name, len(self._sockets)) self._sockets[socket.index] = socket def _add_one_respawn_road(self, respawn_road: Road): assert isinstance(respawn_road, Road), "Spawn roads list only accept Road Type" self._respawn_roads.append(respawn_road) def _clear_topology(self): self._global_network -= self.block_network self.block_network.graph.clear() self.PART_IDX = 0 self.ROAD_IDX = 0 self._respawn_roads.clear() self._sockets.clear() def _try_plug_into_previous_block(self) -> bool: """ Try to plug this Block to previous block's socket, return True for success, False for road cross """ raise NotImplementedError """------------------------------------- For Render and Physics Calculation ---------------------------------- """ def _create_in_world(self): """ Create NodePath and Geom node to perform both collision detection and render """ self.lane_line_node_path = NodePath( RigidBodyCombiner(self._block_name + "_lane_line")) self.sidewalk_node_path = NodePath( RigidBodyCombiner(self._block_name + "_sidewalk")) self.lane_node_path = NodePath( RigidBodyCombiner(self._block_name + "_lane")) self.lane_vis_node_path = NodePath( RigidBodyCombiner(self._block_name + "_lane_vis")) graph = self.block_network.graph for _from, to_dict in graph.items(): for _to, lanes in to_dict.items(): self._add_lane_surface(_from, _to, lanes) for _id, l in enumerate(lanes): line_color = l.line_color self._add_lane(l, _id, line_color) self.lane_line_node_path.flattenStrong() self.lane_line_node_path.node().collect() self.sidewalk_node_path.flattenStrong() self.sidewalk_node_path.node().collect() self.sidewalk_node_path.hide(CamMask.ScreenshotCam) # only bodies reparent to this node self.lane_node_path.flattenStrong() self.lane_node_path.node().collect() self.lane_vis_node_path.flattenStrong() self.lane_vis_node_path.node().collect() self.lane_vis_node_path.hide(CamMask.DepthCam | CamMask.ScreenshotCam) self.node_path = NodePath(self._block_name) self.node_path.hide(CamMask.Shadow) self.sidewalk_node_path.reparentTo(self.node_path) self.lane_line_node_path.reparentTo(self.node_path) self.lane_node_path.reparentTo(self.node_path) self.lane_vis_node_path.reparentTo(self.node_path) self.bounding_box = self.block_network.get_bounding_box() def _add_lane(self, lane: AbstractLane, lane_id: int, colors: List[Vec4]): parent_np = self.lane_line_node_path lane_width = lane.width_at(0) for k, i in enumerate([-1, 1]): line_color = colors[k] if lane.line_types[k] == LineType.NONE or (lane_id != 0 and k == 0): if isinstance(lane, StraightLane): continue elif isinstance( lane, CircularLane) and lane.radius != lane_width / 2: # for ramp render continue if lane.line_types[k] == LineType.CONTINUOUS or lane.line_types[ k] == LineType.SIDE: if isinstance(lane, StraightLane): lane_start = lane.position(0, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = lane.position(lane.length / 2, i * lane_width / 2) self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) elif isinstance(lane, CircularLane): segment_num = int(lane.length / Block.CIRCULAR_SEGMENT_LENGTH) for segment in range(segment_num): lane_start = lane.position( segment * Block.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) lane_end = lane.position( (segment + 1) * Block.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) # for last part lane_start = lane.position( segment_num * Block.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) if lane.line_types[k] == LineType.SIDE: radius = lane.radius if isinstance(lane, CircularLane) else 0.0 segment_num = int(lane.length / Block.SIDEWALK_LENGTH) for segment in range(segment_num): lane_start = lane.position( segment * Block.SIDEWALK_LENGTH, i * lane_width / 2) lane_end = lane.position( (segment + 1) * Block.SIDEWALK_LENGTH, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_sidewalk2bullet(lane_start, lane_end, middle, radius, lane.direction) # for last part lane_start = lane.position( segment_num * Block.SIDEWALK_LENGTH, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = (lane_start + lane_end) / 2 if norm(lane_start[0] - lane_end[0], lane_start[1] - lane_end[1]) > 1e-1: self._add_sidewalk2bullet(lane_start, lane_end, middle, radius, lane.direction) elif lane.line_types[k] == LineType.BROKEN: straight = True if isinstance(lane, StraightLane) else False segment_num = int(lane.length / (2 * Block.STRIPE_LENGTH)) for segment in range(segment_num): lane_start = lane.position( segment * Block.STRIPE_LENGTH * 2, i * lane_width / 2) lane_end = lane.position( segment * Block.STRIPE_LENGTH * 2 + Block.STRIPE_LENGTH, i * lane_width / 2) middle = lane.position( segment * Block.STRIPE_LENGTH * 2 + Block.STRIPE_LENGTH / 2, i * lane_width / 2) self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k], straight) lane_start = lane.position( segment_num * Block.STRIPE_LENGTH * 2, i * lane_width / 2) lane_end = lane.position(lane.length + Block.STRIPE_LENGTH, i * lane_width / 2) middle = (lane_end[0] + lane_start[0]) / 2, (lane_end[1] + lane_start[1]) / 2 if not straight: self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k], straight) if straight: lane_start = lane.position(0, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = lane.position(lane.length / 2, i * lane_width / 2) self._add_box_body(lane_start, lane_end, middle, parent_np, lane.line_types[k], line_color) def _add_box_body(self, lane_start, lane_end, middle, parent_np: NodePath, line_type, line_color): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) if LineType.prohibit(line_type): node_name = BodyName.White_continuous_line if line_color == LineColor.GREY else BodyName.Yellow_continuous_line else: node_name = BodyName.Broken_line body_node = BulletGhostNode(node_name) body_node.set_active(False) body_node.setKinematic(False) body_node.setStatic(True) body_np = parent_np.attachNewNode(body_node) shape = BulletBoxShape( Vec3(length / 2, Block.LANE_LINE_WIDTH / 2, Block.LANE_LINE_GHOST_HEIGHT)) body_np.node().addShape(shape) mask = Block.CONTINUOUS_COLLISION_MASK if line_type != LineType.BROKEN else Block.BROKEN_COLLISION_MASK body_np.node().setIntoCollideMask(BitMask32.bit(mask)) self.static_nodes.append(body_np.node()) body_np.setPos(panda_position(middle, Block.LANE_LINE_GHOST_HEIGHT / 2)) direction_v = lane_end - lane_start theta = -numpy.arctan2(direction_v[1], direction_v[0]) body_np.setQuat( LQuaternionf(numpy.cos(theta / 2), 0, 0, numpy.sin(theta / 2))) def _add_lane_line2bullet(self, lane_start, lane_end, middle, parent_np: NodePath, color: Vec4, line_type: LineType, straight_stripe=False): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) if length <= 0: return if LineType.prohibit(line_type): node_name = BodyName.White_continuous_line if color == LineColor.GREY else BodyName.Yellow_continuous_line else: node_name = BodyName.Broken_line # add bullet body for it if straight_stripe: body_np = parent_np.attachNewNode(node_name) else: body_node = BulletGhostNode(node_name) body_node.set_active(False) body_node.setKinematic(False) body_node.setStatic(True) body_np = parent_np.attachNewNode(body_node) # its scale will change by setScale body_height = Block.LANE_LINE_GHOST_HEIGHT shape = BulletBoxShape( Vec3(length / 2 if line_type != LineType.BROKEN else length, Block.LANE_LINE_WIDTH / 2, body_height)) body_np.node().addShape(shape) mask = Block.CONTINUOUS_COLLISION_MASK if line_type != LineType.BROKEN else Block.BROKEN_COLLISION_MASK body_np.node().setIntoCollideMask(BitMask32.bit(mask)) self.static_nodes.append(body_np.node()) # position and heading body_np.setPos(panda_position(middle, Block.LANE_LINE_GHOST_HEIGHT / 2)) direction_v = lane_end - lane_start theta = -numpy.arctan2(direction_v[1], direction_v[0]) body_np.setQuat( LQuaternionf(numpy.cos(theta / 2), 0, 0, numpy.sin(theta / 2))) if self.render: # For visualization lane_line = self.loader.loadModel( AssetLoader.file_path("models", "box.bam")) lane_line.setScale(length, Block.LANE_LINE_WIDTH, Block.LANE_LINE_THICKNESS) lane_line.setPos(Vec3(0, 0 - Block.LANE_LINE_GHOST_HEIGHT / 2)) lane_line.reparentTo(body_np) body_np.set_color(color) def _add_sidewalk2bullet(self, lane_start, lane_end, middle, radius=0.0, direction=0): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) body_node = BulletRigidBodyNode(BodyName.Sidewalk) body_node.set_active(False) body_node.setKinematic(False) body_node.setStatic(True) side_np = self.sidewalk_node_path.attachNewNode(body_node) shape = BulletBoxShape(Vec3(1 / 2, 1 / 2, 1 / 2)) body_node.addShape(shape) body_node.setIntoCollideMask( BitMask32.bit(self.CONTINUOUS_COLLISION_MASK)) self.dynamic_nodes.append(body_node) if radius == 0: factor = 1 else: if direction == 1: factor = (1 - self.SIDEWALK_LINE_DIST / radius) else: factor = (1 + self.SIDEWALK_WIDTH / radius) * ( 1 + self.SIDEWALK_LINE_DIST / radius) direction_v = lane_end - lane_start vertical_v = PGVector( (-direction_v[1], direction_v[0])) / norm(*direction_v) middle += vertical_v * (self.SIDEWALK_WIDTH / 2 + self.SIDEWALK_LINE_DIST) side_np.setPos(panda_position(middle, 0)) theta = -numpy.arctan2(direction_v[1], direction_v[0]) side_np.setQuat( LQuaternionf(numpy.cos(theta / 2), 0, 0, numpy.sin(theta / 2))) side_np.setScale( length * factor, self.SIDEWALK_WIDTH, self.SIDEWALK_THICKNESS * (1 + 0.1 * numpy.random.rand())) if self.render: side_np.setTexture(self.ts_color, self.side_texture) self.sidewalk.instanceTo(side_np) def _add_lane_surface(self, from_: str, to_: str, lanes: List): """ Add the land surface to world, this surface will record the lane information, like index :param from_: From node :param to_: To Node :param lanes: All lanes of this road :return: None """ # decoration only has vis properties need_body = False if (from_, to_) == (Decoration.start, Decoration.end) else True if isinstance(lanes[0], StraightLane): for index, lane in enumerate(lanes): middle = lane.position(lane.length / 2, 0) end = lane.position(lane.length, 0) direction_v = end - middle theta = -numpy.arctan2(direction_v[1], direction_v[0]) width = lane.width_at(0) + self.SIDEWALK_LINE_DIST * 2 length = lane.length self._add_lane2bullet(middle, width, length, theta, lane, (from_, to_, index)) else: for index, lane in enumerate(lanes): segment_num = int(lane.length / self.CIRCULAR_SEGMENT_LENGTH) for i in range(segment_num): middle = lane.position( lane.length * (i + .5) / segment_num, 0) end = lane.position(lane.length * (i + 1) / segment_num, 0) direction_v = end - middle theta = -numpy.arctan2(direction_v[1], direction_v[0]) width = lane.width_at(0) + self.SIDEWALK_LINE_DIST * 2 length = lane.length self._add_lane2bullet(middle, width, length * 1.3 / segment_num, theta, lane, (from_, to_, index)) def _add_lane2bullet(self, middle, width, length, theta, lane: Union[StraightLane, CircularLane], lane_index): """ Add lane visualization and body for it :param middle: Middle point :param width: Lane width :param length: Segment length :param theta: Rotate theta :param lane: Lane info :return: None """ segment_np = NodePath(LaneNode(BodyName.Lane, lane, lane_index)) segment_node = segment_np.node() segment_node.set_active(False) segment_node.setKinematic(False) segment_node.setStatic(True) shape = BulletBoxShape(Vec3(length / 2, 0.1, width / 2)) segment_node.addShape(shape) self.static_nodes.append(segment_node) segment_np.setPos(panda_position(middle, -0.1)) segment_np.setQuat( LQuaternionf( numpy.cos(theta / 2) * numpy.cos(-numpy.pi / 4), numpy.cos(theta / 2) * numpy.sin(-numpy.pi / 4), -numpy.sin(theta / 2) * numpy.cos(-numpy.pi / 4), numpy.sin(theta / 2) * numpy.cos(-numpy.pi / 4))) segment_np.reparentTo(self.lane_node_path) if self.render: cm = CardMaker('card') cm.setFrame(-length / 2, length / 2, -width / 2, width / 2) cm.setHasNormals(True) cm.setUvRange((0, 0), (length / 20, width / 10)) card = self.lane_vis_node_path.attachNewNode(cm.generate()) card.setPos( panda_position(middle, numpy.random.rand() * 0.01 - 0.01)) card.setQuat( LQuaternionf( numpy.cos(theta / 2) * numpy.cos(-numpy.pi / 4), numpy.cos(theta / 2) * numpy.sin(-numpy.pi / 4), -numpy.sin(theta / 2) * numpy.cos(-numpy.pi / 4), numpy.sin(theta / 2) * numpy.cos(-numpy.pi / 4))) card.setTransparency(TransparencyAttrib.MMultisample) card.setTexture(self.ts_color, self.road_texture) @staticmethod def create_socket_from_positive_road(road: Road) -> BlockSocket: """ We usually create road from positive road, thus this func can get socket easily. Note: it is not recommended to generate socket from negative road """ assert road.start_node[0] != Road.NEGATIVE_DIR and road.end_node[0] != Road.NEGATIVE_DIR, \ "Socket can only be created from positive road" positive_road = Road(road.start_node, road.end_node) return BlockSocket(positive_road, -positive_road) def get_socket_indices(self): return list(self._sockets.keys()) def get_socket_list(self): return list(self._sockets.values())
class BaseBlock(BaseObject, DrivableAreaProperty): """ Block is a driving area consisting of several roads Note: overriding the _sample() function to fill block_network/respawn_roads in subclass Call Block.construct_block() to add it to world """ ID = "B" def __init__(self, block_index: int, global_network: RoadNetwork, random_seed, ignore_intersection_checking=False): super(BaseBlock, self).__init__(str(block_index) + self.ID, random_seed, escape_random_seed_assertion=True) # block information assert self.ID is not None, "Each Block must has its unique ID When define Block" assert len(self.ID) == 1, "Block ID must be a character " self.block_index = block_index self.ignore_intersection_checking = ignore_intersection_checking # each block contains its own road network and a global network self._global_network = global_network self.block_network = RoadNetwork() # a bounding box used to improve efficiency x_min, x_max, y_min, y_max self.bounding_box = None # used to spawn npc self._respawn_roads = [] self._block_objects = None if self.render: # render pre-load self.road_texture = self.loader.loadTexture( AssetLoader.file_path("textures", "sci", "color.jpg")) self.road_texture.setMinfilter( SamplerState.FT_linear_mipmap_linear) self.road_texture.setAnisotropicDegree(8) self.road_normal = self.loader.loadTexture( AssetLoader.file_path("textures", "sci", "normal.jpg")) self.ts_color = TextureStage("color") self.ts_normal = TextureStage("normal") self.side_texture = self.loader.loadTexture( AssetLoader.file_path("textures", "sidewalk", "color.png")) self.side_texture.setMinfilter( SamplerState.FT_linear_mipmap_linear) self.side_texture.setAnisotropicDegree(8) self.side_normal = self.loader.loadTexture( AssetLoader.file_path("textures", "sidewalk", "normal.png")) self.sidewalk = self.loader.loadModel( AssetLoader.file_path("models", "box.bam")) def _sample_topology(self) -> bool: """ Sample a new topology to fill self.block_network """ raise NotImplementedError def construct_block(self, root_render_np: NodePath, physics_world: PhysicsWorld, extra_config: Dict = None, no_same_node=True) -> bool: """ Randomly Construct a block, if overlap return False """ self.sample_parameters() self.origin = NodePath(self.name) self._block_objects = [] if extra_config: assert set(extra_config.keys()).issubset(self.PARAMETER_SPACE.parameters), \ "Make sure the parameters' name are as same as what defined in pg_space.py" raw_config = self.get_config() raw_config.update(extra_config) self.update_config(raw_config) self._clear_topology() success = self._sample_topology() self._global_network.add(self.block_network, no_same_node) self._create_in_world() self.attach_to_world(root_render_np, physics_world) return success def destruct_block(self, physics_world: PhysicsWorld): self._clear_topology() self.detach_from_world(physics_world) self.origin.removeNode() self.dynamic_nodes.clear() self.static_nodes.clear() for obj in self._block_objects: obj.destroy() self._block_objects = None def construct_from_config(self, config: Dict, root_render_np: NodePath, physics_world: PhysicsWorld): success = self.construct_block(root_render_np, physics_world, config) return success def get_respawn_roads(self): return self._respawn_roads def get_respawn_lanes(self): """ return a 2-dim array [[]] to keep the lane index """ ret = [] for road in self._respawn_roads: lanes = road.get_lanes(self.block_network) ret.append(lanes) return ret def get_intermediate_spawn_lanes(self): """Return all lanes that can be used to generate spawn intermediate vehicles.""" raise NotImplementedError() def _add_one_respawn_road(self, respawn_road: Road): assert isinstance(respawn_road, Road), "Spawn roads list only accept Road Type" self._respawn_roads.append(respawn_road) def _clear_topology(self): self._global_network -= self.block_network self.block_network.graph.clear() self.PART_IDX = 0 self.ROAD_IDX = 0 self._respawn_roads.clear() """------------------------------------- For Render and Physics Calculation ---------------------------------- """ def _create_in_world(self): """ Create NodePath and Geom node to perform both collision detection and render """ self.lane_line_node_path = NodePath( RigidBodyCombiner(self.name + "_lane_line")) self.sidewalk_node_path = NodePath( RigidBodyCombiner(self.name + "_sidewalk")) self.lane_node_path = NodePath(RigidBodyCombiner(self.name + "_lane")) self.lane_vis_node_path = NodePath( RigidBodyCombiner(self.name + "_lane_vis")) graph = self.block_network.graph for _from, to_dict in graph.items(): for _to, lanes in to_dict.items(): self._add_lane_surface(_from, _to, lanes) for _id, l in enumerate(lanes): line_color = l.line_color self._add_lane(l, _id, line_color) self.lane_line_node_path.flattenStrong() self.lane_line_node_path.node().collect() self.sidewalk_node_path.flattenStrong() self.sidewalk_node_path.node().collect() self.sidewalk_node_path.hide(CamMask.ScreenshotCam) # only bodies reparent to this node self.lane_node_path.flattenStrong() self.lane_node_path.node().collect() self.lane_vis_node_path.flattenStrong() self.lane_vis_node_path.node().collect() self.lane_vis_node_path.hide(CamMask.DepthCam | CamMask.ScreenshotCam) self.origin.hide(CamMask.Shadow) self.sidewalk_node_path.reparentTo(self.origin) self.lane_line_node_path.reparentTo(self.origin) self.lane_node_path.reparentTo(self.origin) self.lane_vis_node_path.reparentTo(self.origin) self.bounding_box = self.block_network.get_bounding_box() def _add_pgdrive_lanes(self, lane, lane_id, lane_width, colors, parent_np): # for pgdrive structure for k, i in enumerate([-1, 1]): line_color = colors[k] if lane.line_types[k] == LineType.NONE or (lane_id != 0 and k == 0): if isinstance(lane, StraightLane): continue elif isinstance( lane, CircularLane) and lane.radius != lane_width / 2: # for ramp render continue if lane.line_types[k] == LineType.CONTINUOUS or lane.line_types[ k] == LineType.SIDE: if isinstance(lane, StraightLane): lane_start = lane.position(0, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = lane.position(lane.length / 2, i * lane_width / 2) self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) elif isinstance(lane, CircularLane): segment_num = int( lane.length / DrivableAreaProperty.CIRCULAR_SEGMENT_LENGTH) for segment in range(segment_num): lane_start = lane.position( segment * DrivableAreaProperty.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) lane_end = lane.position( (segment + 1) * DrivableAreaProperty.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) # for last part lane_start = lane.position( segment_num * DrivableAreaProperty.CIRCULAR_SEGMENT_LENGTH, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k]) if lane.line_types[k] == LineType.SIDE: radius = lane.radius if isinstance(lane, CircularLane) else 0.0 segment_num = int(lane.length / DrivableAreaProperty.SIDEWALK_LENGTH) for segment in range(segment_num): lane_start = lane.position( segment * DrivableAreaProperty.SIDEWALK_LENGTH, i * lane_width / 2) lane_end = lane.position( (segment + 1) * DrivableAreaProperty.SIDEWALK_LENGTH, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_sidewalk2bullet(lane_start, lane_end, middle, radius, lane.direction) # for last part lane_start = lane.position( segment_num * DrivableAreaProperty.SIDEWALK_LENGTH, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = (lane_start + lane_end) / 2 if norm(lane_start[0] - lane_end[0], lane_start[1] - lane_end[1]) > 1e-1: self._add_sidewalk2bullet(lane_start, lane_end, middle, radius, lane.direction) elif lane.line_types[k] == LineType.BROKEN: straight = True if isinstance(lane, StraightLane) else False segment_num = int(lane.length / (2 * DrivableAreaProperty.STRIPE_LENGTH)) for segment in range(segment_num): lane_start = lane.position( segment * DrivableAreaProperty.STRIPE_LENGTH * 2, i * lane_width / 2) lane_end = lane.position( segment * DrivableAreaProperty.STRIPE_LENGTH * 2 + DrivableAreaProperty.STRIPE_LENGTH, i * lane_width / 2) middle = lane.position( segment * DrivableAreaProperty.STRIPE_LENGTH * 2 + DrivableAreaProperty.STRIPE_LENGTH / 2, i * lane_width / 2) self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k], straight) lane_start = lane.position( segment_num * DrivableAreaProperty.STRIPE_LENGTH * 2, i * lane_width / 2) lane_end = lane.position( lane.length + DrivableAreaProperty.STRIPE_LENGTH, i * lane_width / 2) middle = (lane_end[0] + lane_start[0]) / 2, (lane_end[1] + lane_start[1]) / 2 if not straight: self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[k], straight) if straight: lane_start = lane.position(0, i * lane_width / 2) lane_end = lane.position(lane.length, i * lane_width / 2) middle = lane.position(lane.length / 2, i * lane_width / 2) self._add_box_body(lane_start, lane_end, middle, parent_np, lane.line_types[k], line_color) def _add_lane(self, lane: AbstractLane, lane_id: int, colors: List[Vec4]): parent_np = self.lane_line_node_path lane_width = lane.width_at(0) if isinstance(lane, CircularLane) or isinstance(lane, StraightLane): self._add_pgdrive_lanes(lane, lane_id, lane_width, colors, parent_np) elif isinstance(lane, WayPointLane): for c, i in enumerate([-1, 1]): line_color = colors[c] acc_length = 0 if lane.line_types[c] != LineType.NONE: for segment in lane.segment_property: lane_start = lane.position(acc_length, i * lane_width / 2) acc_length += segment["length"] lane_end = lane.position(acc_length, i * lane_width / 2) middle = (lane_start + lane_end) / 2 self._add_lane_line2bullet(lane_start, lane_end, middle, parent_np, line_color, lane.line_types[c]) def _add_box_body(self, lane_start, lane_end, middle, parent_np: NodePath, line_type, line_color): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) if LineType.prohibit(line_type): node_name = BodyName.White_continuous_line if line_color == LineColor.GREY else BodyName.Yellow_continuous_line else: node_name = BodyName.Broken_line body_node = BulletGhostNode(node_name) body_node.set_active(False) body_node.setKinematic(False) body_node.setStatic(True) body_np = parent_np.attachNewNode(body_node) shape = BulletBoxShape( Vec3(length / 2, DrivableAreaProperty.LANE_LINE_WIDTH / 2, DrivableAreaProperty.LANE_LINE_GHOST_HEIGHT)) body_np.node().addShape(shape) mask = DrivableAreaProperty.CONTINUOUS_COLLISION_MASK if line_type != LineType.BROKEN else DrivableAreaProperty.BROKEN_COLLISION_MASK body_np.node().setIntoCollideMask(mask) self.static_nodes.append(body_np.node()) body_np.setPos( panda_position(middle, DrivableAreaProperty.LANE_LINE_GHOST_HEIGHT / 2)) direction_v = lane_end - lane_start # theta = -numpy.arctan2(direction_v[1], direction_v[0]) theta = -math.atan2(direction_v[1], direction_v[0]) body_np.setQuat( LQuaternionf(math.cos(theta / 2), 0, 0, math.sin(theta / 2))) def _add_lane_line2bullet(self, lane_start, lane_end, middle, parent_np: NodePath, color: Vec4, line_type: LineType, straight_stripe=False): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) if length <= 0: return if LineType.prohibit(line_type): node_name = BodyName.White_continuous_line if color == LineColor.GREY else BodyName.Yellow_continuous_line else: node_name = BodyName.Broken_line # add bullet body for it if straight_stripe: body_np = parent_np.attachNewNode(node_name) else: body_node = BulletGhostNode(node_name) body_node.set_active(False) body_node.setKinematic(False) body_node.setStatic(True) body_np = parent_np.attachNewNode(body_node) # its scale will change by setScale body_height = DrivableAreaProperty.LANE_LINE_GHOST_HEIGHT shape = BulletBoxShape( Vec3(length / 2 if line_type != LineType.BROKEN else length, DrivableAreaProperty.LANE_LINE_WIDTH / 2, body_height)) body_np.node().addShape(shape) mask = DrivableAreaProperty.CONTINUOUS_COLLISION_MASK if line_type != LineType.BROKEN else DrivableAreaProperty.BROKEN_COLLISION_MASK body_np.node().setIntoCollideMask(mask) self.static_nodes.append(body_np.node()) # position and heading body_np.setPos( panda_position(middle, DrivableAreaProperty.LANE_LINE_GHOST_HEIGHT / 2)) direction_v = lane_end - lane_start # theta = -numpy.arctan2(direction_v[1], direction_v[0]) theta = -math.atan2(direction_v[1], direction_v[0]) body_np.setQuat( LQuaternionf(math.cos(theta / 2), 0, 0, math.sin(theta / 2))) if self.render: # For visualization lane_line = self.loader.loadModel( AssetLoader.file_path("models", "box.bam")) lane_line.setScale(length, DrivableAreaProperty.LANE_LINE_WIDTH, DrivableAreaProperty.LANE_LINE_THICKNESS) lane_line.setPos( Vec3(0, 0 - DrivableAreaProperty.LANE_LINE_GHOST_HEIGHT / 2)) lane_line.reparentTo(body_np) body_np.set_color(color) def _add_sidewalk2bullet(self, lane_start, lane_end, middle, radius=0.0, direction=0): length = norm(lane_end[0] - lane_start[0], lane_end[1] - lane_start[1]) body_node = BulletRigidBodyNode(BodyName.Sidewalk) body_node.setKinematic(False) body_node.setStatic(True) side_np = self.sidewalk_node_path.attachNewNode(body_node) shape = BulletBoxShape(Vec3(1 / 2, 1 / 2, 1 / 2)) body_node.addShape(shape) body_node.setIntoCollideMask(self.SIDEWALK_COLLISION_MASK) self.dynamic_nodes.append(body_node) if radius == 0: factor = 1 else: if direction == 1: factor = (1 - self.SIDEWALK_LINE_DIST / radius) else: factor = (1 + self.SIDEWALK_WIDTH / radius) * ( 1 + self.SIDEWALK_LINE_DIST / radius) direction_v = lane_end - lane_start vertical_v = Vector( (-direction_v[1], direction_v[0])) / norm(*direction_v) middle += vertical_v * (self.SIDEWALK_WIDTH / 2 + self.SIDEWALK_LINE_DIST) side_np.setPos(panda_position(middle, 0)) theta = -math.atan2(direction_v[1], direction_v[0]) side_np.setQuat( LQuaternionf(math.cos(theta / 2), 0, 0, math.sin(theta / 2))) side_np.setScale( length * factor, self.SIDEWALK_WIDTH, self.SIDEWALK_THICKNESS * (1 + 0.1 * np.random.rand())) if self.render: side_np.setTexture(self.ts_color, self.side_texture) self.sidewalk.instanceTo(side_np) def _add_lane_surface(self, from_: str, to_: str, lanes: List): """ Add the land surface to world, this surface will record the lane information, like index :param from_: From node :param to_: To Node :param lanes: All lanes of this road """ if isinstance(lanes[0], StraightLane): for index, lane in enumerate(lanes): middle = lane.position(lane.length / 2, 0) end = lane.position(lane.length, 0) direction_v = end - middle theta = -math.atan2(direction_v[1], direction_v[0]) width = lane.width_at(0) + self.SIDEWALK_LINE_DIST * 2 length = lane.length self._add_lane2bullet(middle, width, length, theta, lane, (from_, to_, index)) elif isinstance(lanes[0], CircularLane): for index, lane in enumerate(lanes): segment_num = int(lane.length / self.CIRCULAR_SEGMENT_LENGTH) for i in range(segment_num): middle = lane.position( lane.length * (i + .5) / segment_num, 0) end = lane.position(lane.length * (i + 1) / segment_num, 0) direction_v = end - middle theta = -math.atan2(direction_v[1], direction_v[0]) width = lane.width_at(0) + self.SIDEWALK_LINE_DIST * 2 length = lane.length self._add_lane2bullet(middle, width, length * 1.3 / segment_num, theta, lane, (from_, to_, index)) elif isinstance(lanes[0], WayPointLane): for index, lane in enumerate(lanes): for segment in lane.segment_property: lane_start = segment["start_point"] lane_end = segment["end_point"] middle = (lane_start + lane_end) / 2 direction_v = lane_end - middle theta = -math.atan2(direction_v[1], direction_v[0]) width = lane.width_at(0) length = segment["length"] self._add_lane2bullet(middle, width, length, theta, lane, (from_, to_, index)) def _add_lane2bullet(self, middle, width, length, theta, lane: Union[StraightLane, CircularLane], lane_index): """ Add lane visualization and body for it :param middle: Middle point :param width: Lane width :param length: Segment length :param theta: Rotate theta :param lane: Lane info :return: None """ length += 0.1 lane.index = lane_index segment_np = NodePath(BaseRigidBodyNode(lane, BodyName.Lane)) segment_node = segment_np.node() segment_node.set_active(False) segment_node.setKinematic(False) segment_node.setStatic(True) shape = BulletBoxShape(Vec3(length / 2, 0.1, width / 2)) segment_node.addShape(shape) self.static_nodes.append(segment_node) segment_np.setPos(panda_position(middle, -0.1)) segment_np.setQuat( LQuaternionf( math.cos(theta / 2) * math.cos(-math.pi / 4), math.cos(theta / 2) * math.sin(-math.pi / 4), -math.sin(theta / 2) * math.cos(-math.pi / 4), math.sin(theta / 2) * math.cos(-math.pi / 4))) segment_np.reparentTo(self.lane_node_path) if self.render: cm = CardMaker('card') cm.setFrame(-length / 2, length / 2, -width / 2, width / 2) cm.setHasNormals(True) cm.setUvRange((0, 0), (length / 20, width / 10)) card = self.lane_vis_node_path.attachNewNode(cm.generate()) card.setPos(panda_position(middle, np.random.rand() * 0.01 - 0.01)) card.setQuat( LQuaternionf( math.cos(theta / 2) * math.cos(-math.pi / 4), math.cos(theta / 2) * math.sin(-math.pi / 4), -math.sin(theta / 2) * math.cos(-math.pi / 4), math.sin(theta / 2) * math.cos(-math.pi / 4))) card.setTransparency(TransparencyAttrib.MMultisample) card.setTexture(self.ts_color, self.road_texture) def add_body(self, physics_body): raise DeprecationWarning( "Different from common objects like vehicle/traffic sign, Block has several bodies!" "Therefore, you should create BulletBody and then add them to self.dynamics_nodes " "manually. See in construct() method")
def load_into_bamfile(meshdata, subfiles, model): """Uses pycollada and panda3d to load meshdata and subfiles and write out to a bam file on disk""" if os.path.isfile(model.bam_file): print 'returning cached bam file' return model.bam_file mesh = load_mesh(meshdata, subfiles) model_name = model.model_json['full_path'].replace('/', '_') if model.model_type == 'progressive' and model.model_subtype == 'full': progressive_stream = model.model_json['metadata']['types'][ 'progressive'].get('progressive_stream') if progressive_stream is not None: print 'LOADING PROGRESSIVE STREAM' data = model.prog_data try: mesh = add_back_pm.add_back_pm(mesh, StringIO(data), 100) print '-----' print 'SUCCESSFULLY ADDED BACK PM' print '-----' except: f = open(model.bam_file, 'w') f.close() raise print 'loading into bamfile', model_name, mesh scene_members = pandacore.getSceneMembers(mesh) print 'got scene members', model_name, mesh rotateNode = GeomNode("rotater") rotatePath = NodePath(rotateNode) matrix = numpy.identity(4) if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP: r = collada.scene.RotateTransform(0, 1, 0, 90) matrix = r.matrix elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP: r = collada.scene.RotateTransform(1, 0, 0, 90) matrix = r.matrix rotatePath.setMat(Mat4(*matrix.T.flatten().tolist())) for geom, renderstate, mat4 in scene_members: node = GeomNode("primitive") node.addGeom(geom) if renderstate is not None: node.setGeomState(0, renderstate) geomPath = rotatePath.attachNewNode(node) geomPath.setMat(mat4) print 'created np', model_name, mesh if model.model_type != 'optimized_unflattened' and model.model_type != 'progressive': print 'ABOUT TO FLATTEN' rotatePath.flattenStrong() print 'DONE FLATTENING' print 'flattened', model_name, mesh wrappedNode = pandacore.centerAndScale(rotatePath) wrappedNode.setName(model_name) wrappedNode.writeBamFile(model.bam_file) print 'saved', model_name, mesh wrappedNode = None return model.bam_file
class Zone(): def __init__(self, world, name, basedir): self.world = world self.name = name self.basedir = basedir self.load_complete = 0 self.world.consoleOut('zone initializing: '+self.name) # store the in memory WLDFile objects that make uo the zone for direct access self.wld_containers = {} # and as a directory self.zone_wld_container = None self.obj1_wld_container = None self.obj2_wld_container = None self.chr_wld_container = None # init our texture manager self.tm = TextureManager() # init our model manager self.mm = ModelManager(self) self.nulltex = Texture() # create dummy exture object for use in non textured polys self.rootNode = NodePath(PandaNode("zone_root")) self.rootNode.reparentTo(render) self.delta_t = 0 # This currently only updates the direct zone sprites def update(self): if self.load_complete != 1: return # print 'update delta_t:', globalClock.getDt() self.delta_t += globalClock.getDt() if self.delta_t > 0.2: self.delta_t = 0 for container in self.wld_containers: for sprite in self.wld_containers[container].animated_sprites: sprite.update() # build the main zone geometry mesh def prepareZoneMesh(self): wld_container = self.wld_containers['zone'] wld_obj = wld_container.wld_file_obj # load the 0x36 bsp region fragments (sub meshes): all these meshes together # make up the main zone geometry for f in wld_obj.fragments.values(): if f.type == 0x36: # print 'adding fragment_36 to main zone mesh' # f.dump() m = Mesh(self.name) m.buildFromFragment(f, wld_container) m.root.reparentTo(self.rootNode) # "hang" the mesh under our root node # --------------------------------------------------------------------- # create the SPRITE objects: these can reference a single texture or # a list of them (for animated textures like water, lava etc.) # we need to step through all entries of the 0x31 list fragment for the zone # We store the SPRITEs using their index within the 0x31 fragments list as the key # because this is exactly how the meshes (0x36 fragments) reference them # The lists them selves are keyed by the 0x31 fragmemts id (=index in the diskfile) def loadSpriteList(self, wld_container, f31): wld = wld_container.wld_file_obj wld_container.sprite_list[f31.id] = {} sprite_list = wld_container.sprite_list[f31.id] idx = 0 for ref30 in f31.nameRefs: sprite_error = 0 sprite = None # print ref30 f30 = wld.getFragment(ref30) # f30.dump() material_name = wld.getName(f30.nameRef) # Note on TRANSPARENCY: as far as I can tell so far, bit 2 in the params1 field of f30 # is the "semi-transparent" indicator used for all types of water surfaces for the old # zones (pre POP? Seems to not work like this in zones like POV for example anymore) # lets go by this theory anyway for now ''' if f30.params1 & 0x00000004: print 'SEMI TRANSPARENT MATERIAL:' f30.dump() ''' # print 'looking up 0x05 fragment with id_plus_1:', f30.frag05Ref # Note that there are frag05Refs inside some 0x30 fragments with value <=0 # these named references seem to point directly to 0x03 texture fragments # instead of the usual indirection chain 0x05->0x04->0x03 # in some instances these point nowhere meaningful at all though. Need to catch all these frag = wld.getFragment(f30.frag05Ref) if frag != None: if frag.type == 0x03: # this is a direct 0x03 ref (see note above) f03 = frag texfile_name = f03.names[0] tx = self.tm.getTexture(texfile_name) if tx != None: # we dont have a sprite def (0x04) for these, so we use the material (0x30) name sprite = Sprite(material_name, idx, f30.params1, self.tm) sprite.addTexture(texfile_name, tx) else: sprite_error = 1 print 'Error in Sprite:', material_name, 'Texture not found:', texfile_name elif frag.type == 0x05: # this is the "normal" indirection chain 0x30->0x05->0x04->0x03 f05 = frag # f05.dump() f04 = wld.getFragment(f05.frag04Ref) # f04.dump() name = wld.getName(f04.nameRef) sprite = Sprite(name, idx, f30.params1, self.tm) sprite.setAnimDelay(f04.params2) for f03ref in f04.frag03Refs: f03 = wld.getFragment(f03ref) # f03.dump() # NOTE that this assumes the zone 0x03 fragments only ever reference one single texture texfile_name = f03.names[0] tx = self.tm.getTexture(texfile_name) if tx != None: sprite.addTexture(texfile_name, tx) else: sprite_error = 1 print 'Error in Sprite:', name, 'Texure not found:', texfile_name else: # This is the "does point nowhere meaningful at all" case # infact the reference points back to the same fragment (circular) # This type of 0x30 fragment seems to only have been used for zone boundary polygons # in the original EQ classic zones # Note that we create a sprite with just a dummy texture in it for these # sprite_error = 1 print 'Warning : Non standard material:%s. Texture ref in 0x30 frag is not type 0x5 or 0x3 but 0x%x' % (material_name, frag.type) # print 'F30 DUMP:' # f30.dump() # print 'Referenced Fragment DUMP:' # frag.dump() # this will be a sprite with just the dummy nulltex textures # we need this so that transparent zonewalls in the very old classic zones work # newer zones have actually textured ("collide.dds") zone walls sprite = Sprite(material_name, idx, f30.params1, self.tm) sprite.addTexture('nulltexture', self.nulltex) else: sprite_error = 1 print 'Error in Sprite: could not resolve frag05ref:%i in 0x30 fragment:%i' % (f30.frag05Ref, f30.id) if sprite_error != 1: # only add error free sprites # sprite.dump() # new style sprite list sprite_list[idx] = sprite if sprite.anim_delay != 0: print("Adding animated sprite to master list " + sprite.name) wld_container.animated_sprites.append(sprite) idx += 1 # need to increment regardless of whether we stored or not # so that the index lookup using the refs in the 0x36's works # preloadWldTextures actually does quite a bit more than just preloading texture files # the main task of this code is to generate our SPRITES # Params # wld_container is a WldContainer object def preloadWldTextures(self, wld_container): self.world.consoleOut('preloading textures for container: '+ wld_container.name) wld = wld_container.wld_file_obj # the in memory wld file # loop over all 0x03 fragments and PRELOAD all referenced texture files from the s3d f31 = None f31_list = [] for f in wld.fragments.values(): if f.type == 0x03: # f.dump() # NOTE # in VERSION 2 WLD zones (ex. povalor, postorms) I've found texture names # that have three parameters prepended like this for example: 1, 4, 0, POVSNOWDET01.DDS # no idea yet as to what these mean but in order to be able to load the texture from # the s3d container we need to strip this stuff for name in f.names: i = name.rfind(',') if i != -1: # See NOTE above print 'parametrized texture name found:%s wld version:0x%x' % (name, self.wldZone.version) name = name[i+1:].strip() self.tm.loadTexture(name.lower(), wld_container) # need to store the 0x31 texture lists if f.type == 0x31: f31_list.append(f) # not all wld files define sprites if len(f31_list) == 0: return for f31 in f31_list: self.loadSpriteList(wld_container, f31) #print("Loaded sprites got this many: " + str(len(wld_container.animated_sprites))) # preload the textures/sprites for all loaded containers def preloadTextures(self): # self.preloadWldTextures(self.zone_wld_container) for wld_obj in self.wld_containers.values(): self.preloadWldTextures(wld_obj) # We let Panda3D "flatten" the plethora of GEOMs we created from the original bsp tree # ==> this process creates a complete new NodePath->GeomNode tree from our original # In order to implement texture animation and transparency we need to map the new Geom's textures # back to our Sprites so we can change texture assignments and transparency on a Geom level in # the new structure # Remap the Textures in use for the main Zone Geometry def remapTextures(self): # ------------------------------------------------------------------------------------- # ANIMATED TEXTURE SETUP AND TRANSPARENCY FOR ZONE GEOMETRY (NOT PLACEABLES etc!) # trying to evaluate the scene graph structure under our root node here # since flattenStrong() totally changes the structure of our scene from how we # originally created it, we need to find a way to: # - get a the geoms that the flatten process has produced # - find their textures # - map those back to our sprites # - and finally set up the update process for texture animations based on the above # # NOTE this code will fail if there is more than one sprite useing a single texture! # Not encountered this yet though. self.world.consoleOut('setting up animated textures for zone geometry') for child in self.rootNode.getChildren(): # print child geom_node = child.node() for geom_number in range(0, geom_node.getNumGeoms()): geom_render_state = geom_node.getGeomState(geom_number) attr = geom_render_state.getAttrib(26) # attrib 26 is the texture attribute (hope this is static) if attr != None: # print attr tex = attr.getTexture() # print tex # BINGO! now we have the texture for this GEOM, lets find the sprite sprite = self.zone_wld_container.findSpriteUsing(tex) if sprite != None: # print sprite # set general texture alpha based tansparency for masked textures # if sprite.masked == 1: # child.setTransparency(TransparencyAttrib.MAlpha) if sprite.transparent == 1 or sprite.masked == 1: # EXPERIMENTAL TRANSPARENCY SUPPORT ############### # This is for semi-transparent polygons (water surfaces etc) # we implement the transparency via the alpha component of the GEOM's ColorAttrib ta = TransparencyAttrib.make(TransparencyAttrib.MAlpha) geom_render_state = geom_render_state.setAttrib(ta, 1) # potentialy needs passing "int override" (=1?) as second param if not sprite.masked == 1: ca = ColorAttrib.makeFlat(Vec4(1, 1, 1, sprite.alpha)) geom_render_state = geom_render_state.setAttrib(ca, 1) # potentialy needs passing "int override" (=1?) as second param geom_node.setGeomState(geom_number, geom_render_state) # ##################################################### if sprite.anim_delay > 0: # ANIMATED SPRITE # sprite.addAnimGeomRenderState((geom_node, geom_number, geom_render_state)) sprite.addAnimGeomRenderState((geom_node, geom_number, geom_render_state)) else: print 'could not find sprite for geom node, node texture cant be animated' # load up everything related to this zone def load(self): # ---- ZONE GEOMETRY ---- # load main zone s3d s3dfile_name = self.name+'.s3d' self.world.consoleOut('zone loading zone s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir+self.name) if s3d.load() != 0: self.world.consoleOut( 'ERROR loading s3dfile:' + self.basedir+s3dfile_name) return -1 # s3d.dumpListing() # load main zone wld wldZone = WLDFile(self.name) # wldZone.setDumpList([0x14, 0x15]) wldZone.load(s3d) self.zone_wld_container = WLDContainer('zone', self, wldZone, s3d) self.wld_containers['zone'] = self.zone_wld_container # load the objects.wld file from the same container # this basically consists of 0x15 model references for putting all placeables in place wldZoneObj = WLDFile('objects') # wldZoneObj.setDumpList([0x14, 0x15]) wldZoneObj.load(s3d) self.zone_obj_wld_container = WLDContainer('zone_obj', self, wldZoneObj, s3d) self.wld_containers['zone_obj'] = self.zone_obj_wld_container # ---- placeables definitions ------------------------------------ s3dfile_name = self.name+'_obj.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading placeable objects s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir+self.name+'_obj') if s3d.load() == 0: # s3d.dumpListing() wldObj1 = WLDFile(self.name+'_obj') wldObj1.setDumpList([0x14, 0x13, 0x12, 0x11, 0x10]) wldObj1.load(s3d) self.obj1_wld_container = WLDContainer('obj', self, wldObj1, s3d) self.wld_containers['obj1'] = self.obj1_wld_container else: self.world.consoleOut( 'zone object 1 s3dfile does not exist:' + self.basedir+s3dfile_name) s3dfile_name = self.name+'_2_obj.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading placeable objects 2 s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir+self.name+'_2_obj') if s3d.load() == 0: # s3d.dumpListing() wldObj2 = WLDFile(self.name+'_2_obj') # wldObj2.setDumpList([0x14, 0x15, 0x2D, 0x36]) wldObj2.load(s3d) self.obj2_wld_container = WLDContainer('obj', self, wldObj2, s3d) self.wld_containers['obj2'] = self.obj2_wld_container else: self.world.consoleOut( 'zone object 2 s3dfile does not exist:' + self.basedir+s3dfile_name) s3dfile_name = self.name+'_chr.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading character s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir+self.name+'_chr') if s3d.load() == 0: # s3d.dumpListing() wldChr = WLDFile(self.name+'_chr') # wldChr.setDumpList([0x14, 0x15, 0x2D, 0x36]) wldChr.load(s3d) self.chr_wld_container = WLDContainer('chr', self, wldChr, s3d) self.wld_containers['chr'] = self.chr_wld_container else: self.world.consoleOut( 'zone character s3dfile does not exist:' + self.basedir+s3dfile_name) # --- TEXTURES ---- self.world.consoleOut('preloading textures') self.preloadTextures() # ---- Generate main Zone Geometry ---- self.world.consoleOut( 'preparing zone mesh') self.prepareZoneMesh() # let Panda3D attempt to flatten the zone geometry (reduce the excessive # Geom count resulting from the layout of the .wld zone data as a huge # bunch of tiny bsp regions) self.world.consoleOut('flattening zone mesh geom tree') # self.rootNode.ls() self.rootNode.flattenStrong() self.rootNode.ls() # texture->sprite remapping after the flatten above self.remapTextures() # COLLISION: # The following makes the complete zone base geometry eligible for collisions # this is of course extremely inefficient. TODO: at some point we need to use the # bsp structures already provided in the wld file to optimize whats in our scene graph # and also to build a more intelligent collision system self.rootNode.setCollideMask(BitMask32.bit(0)) # ---- load MODELS and spawn placeables ----------------------- # go through all the 0x15 refs and create empty model "shells" # for every unique entry self.world.consoleOut( 'loading placeables models') self.mm.loadPlaceables(wldZoneObj) # self.rootNode.ls() print 'zone load complete' self.load_complete = 1 return 0
class TileMap(): def __init__(self, tileMap=None): self.np = NodePath("TileMap") self._tileModel = base.loader.loadModel(MODEL_FILE_PATH) self._tileTexture = base.loader.loadTexture(TEXTURE_PATH) self._tileModel.setTexture(self._tileTexture) self._tileMap = dict() self._tileMap2D = None self._generateMap( tileMap) # Create our map and fill np with real geometry. self._tileModel.clearModelNodes() # Mandatory processing of the model self.np.flattenStrong() # Used to optimize rendering. self.np.reparentTo(render) # Start rendering this map def _generateMap(self, tileMap=None): """ Generates a new map (or a given one) for rendering. Optionally takes in a flat string representation (Use in networking) """ if tileMap == None: # Create 2d list dungeon with pybsp: newDungeon = pybsp.generateDungeon2DList((100, 100), (20, 20)) self._tileMap2D = newDungeon else: tileMap = convertDungeonFromString(tileMap, 100) self._tileMap2D = tileMap newDungeon = tileMap # Use the output to fill our tileMap dict. for row in range(len(newDungeon)): for col in range(len(newDungeon[row])): # Values hold whether the tile is a floor or not, # what creatures or blocking objects it holds # and what items occupy the space. self._tileMap[Point2D(row, col)] = [newDungeon[row][col], [], []] if newDungeon[row][ col] == 1: # Create tile models along the way placeholder = self.np.attachNewNode("Tile(%s,%s)"\ %(row, col)) placeholder.setPos(row * TILE_SIZE, col * TILE_SIZE, 1) self._tileModel.instanceTo(placeholder) def isFloor(self, coords): """ Returns whether the given Point2D is a valid tile in the tileMap """ if coords in self._tileMap.keys(): if self._tileMap[coords][0] == 1: return True return False def isTileOccupied(self, coords): """ Returns whether the tile at Point2D coords is occupied or exists. If the tile is free and exists, this returns False. """ # Check if tile is a floor: if self.isFloor(coords): # Check if the tile is free of characters/obstructions: tile = self._tileMap[coords] if len(tile[1]) == 0: return False # Tile exists and free! return True # Tile doesn't exist at this spot or is occupied. def getTileMap(self): return self._tileMap def getCreatureAtPos(self, coords): """ Returns the first creature object found at coords, if there are any. If there are no creatures at the specified coords, returns None. """ if self.isFloor(coords) and self.isTileOccupied(coords): creaturesList = self._tileMap[coords][1] for obj in creaturesList: if isinstance(obj, Creature): return obj # Automatically returns None def updateObjectLocation(self, node, oldLocation, newLocation): """ Finds the node at old location and moves it to newLocation. """ if node in self._tileMap[oldLocation][1]: self._tileMap[oldLocation][1].remove(node) self._tileMap[newLocation][1].append(node) def spawnObject(self, node, newLocation): self._tileMap[newLocation][1].append(node) def spawnItem(self, item, newLocation): self._tileMap[newLocation][2].append(item) def pickupItem(self, item): self._tileMap[item.getGridPosition()][2].remove(item) def getItemsAtPosition(self, position): """ Returns the list of items at a given position """ return self._tileMap[position][2] def despawnCreature(self, creature): """ Removes the creature from this tileMap """ self._tileMap[creature.getGridPosition()][1].remove(creature) def getCharactersAroundPoint(self, point, reach): """ Returns any creatures around point within range. """ creatureList = list() tilesToSearch = getAreaTiles(point, self, reach) for tile in tilesToSearch: creature = self.getCreatureAtPos(tile) if creature: creatureList.append(creature) return creatureList def findAdjacentOpenSpaces(self, point): """ Returns points around point that are empty floors. """ openSpaces = list() tilesToSearch = getAreaTiles(point, self, 1) for tile in tilesToSearch: creature = self.getCreatureAtPos(tile) if not creature: openSpaces.append(tile) return openSpaces def getTileMapStr(self): """ Used to send map layout across the network. Essentially flattens the 2D list into a string of 0s and 1s. """ newString = "".join( str(tile) for row in self._tileMap2D for tile in row) return newString def getRandomFloor(self): """ Picks a random floor from the dict. Assumes the dungeon has been initialized. """ validKeys = [] for key in self._tileMap.keys(): if self._tileMap[key][0] == 1: validKeys.append(key) return validKeys[random.randint(0, len(validKeys) - 1)] # Random inclusive def getRandomEmptyFloor(self): """ Picks a random empty floor from the dict. Assumes the dungeon has been initialized. """ validKeys = [] for key in self._tileMap.keys(): if self._tileMap[key][0] == 1 and len(self._tileMap[key][1]) == 0: validKeys.append(key) return validKeys[random.randint(0, len(validKeys) - 1)] # Random inclusive
class Zone(): def __init__(self, world, name, basedir): self.world = world self.name = name self.basedir = basedir self.load_complete = 0 self.world.consoleOut('zone initializing: ' + self.name) # store the in memory WLDFile objects that make uo the zone for direct access self.wld_containers = {} # and as a directory self.zone_wld_container = None self.obj1_wld_container = None self.obj2_wld_container = None self.chr_wld_container = None # init our texture manager self.tm = TextureManager() # init our model manager self.mm = ModelManager(self) self.nulltex = Texture( ) # create dummy exture object for use in non textured polys self.rootNode = NodePath(PandaNode("zone_root")) self.rootNode.reparentTo(render) self.delta_t = 0 # This currently only updates the direct zone sprites def update(self): if self.load_complete != 1: return # print 'update delta_t:', globalClock.getDt() self.delta_t += globalClock.getDt() if self.delta_t > 0.2: self.delta_t = 0 for container in self.wld_containers: for sprite in self.wld_containers[container].animated_sprites: sprite.update() # build the main zone geometry mesh def prepareZoneMesh(self): wld_container = self.wld_containers['zone'] wld_obj = wld_container.wld_file_obj # load the 0x36 bsp region fragments (sub meshes): all these meshes together # make up the main zone geometry for f in wld_obj.fragments.values(): if f.type == 0x36: # print 'adding fragment_36 to main zone mesh' # f.dump() m = Mesh(self.name) m.buildFromFragment(f, wld_container) m.root.reparentTo( self.rootNode) # "hang" the mesh under our root node # --------------------------------------------------------------------- # create the SPRITE objects: these can reference a single texture or # a list of them (for animated textures like water, lava etc.) # we need to step through all entries of the 0x31 list fragment for the zone # We store the SPRITEs using their index within the 0x31 fragments list as the key # because this is exactly how the meshes (0x36 fragments) reference them # The lists them selves are keyed by the 0x31 fragmemts id (=index in the diskfile) def loadSpriteList(self, wld_container, f31): wld = wld_container.wld_file_obj wld_container.sprite_list[f31.id] = {} sprite_list = wld_container.sprite_list[f31.id] idx = 0 for ref30 in f31.nameRefs: sprite_error = 0 sprite = None # print ref30 f30 = wld.getFragment(ref30) # f30.dump() material_name = wld.getName(f30.nameRef) # Note on TRANSPARENCY: as far as I can tell so far, bit 2 in the params1 field of f30 # is the "semi-transparent" indicator used for all types of water surfaces for the old # zones (pre POP? Seems to not work like this in zones like POV for example anymore) # lets go by this theory anyway for now ''' if f30.params1 & 0x00000004: print 'SEMI TRANSPARENT MATERIAL:' f30.dump() ''' # print 'looking up 0x05 fragment with id_plus_1:', f30.frag05Ref # Note that there are frag05Refs inside some 0x30 fragments with value <=0 # these named references seem to point directly to 0x03 texture fragments # instead of the usual indirection chain 0x05->0x04->0x03 # in some instances these point nowhere meaningful at all though. Need to catch all these frag = wld.getFragment(f30.frag05Ref) if frag != None: if frag.type == 0x03: # this is a direct 0x03 ref (see note above) f03 = frag texfile_name = f03.names[0] tx = self.tm.getTexture(texfile_name) if tx != None: # we dont have a sprite def (0x04) for these, so we use the material (0x30) name sprite = Sprite(material_name, idx, f30.params1, self.tm) sprite.addTexture(texfile_name, tx) else: sprite_error = 1 print 'Error in Sprite:', material_name, 'Texture not found:', texfile_name elif frag.type == 0x05: # this is the "normal" indirection chain 0x30->0x05->0x04->0x03 f05 = frag # f05.dump() f04 = wld.getFragment(f05.frag04Ref) # f04.dump() name = wld.getName(f04.nameRef) sprite = Sprite(name, idx, f30.params1, self.tm) sprite.setAnimDelay(f04.params2) for f03ref in f04.frag03Refs: f03 = wld.getFragment(f03ref) # f03.dump() # NOTE that this assumes the zone 0x03 fragments only ever reference one single texture texfile_name = f03.names[0] tx = self.tm.getTexture(texfile_name) if tx != None: sprite.addTexture(texfile_name, tx) else: sprite_error = 1 print 'Error in Sprite:', name, 'Texure not found:', texfile_name else: # This is the "does point nowhere meaningful at all" case # infact the reference points back to the same fragment (circular) # This type of 0x30 fragment seems to only have been used for zone boundary polygons # in the original EQ classic zones # Note that we create a sprite with just a dummy texture in it for these # sprite_error = 1 print 'Warning : Non standard material:%s. Texture ref in 0x30 frag is not type 0x5 or 0x3 but 0x%x' % ( material_name, frag.type) # print 'F30 DUMP:' # f30.dump() # print 'Referenced Fragment DUMP:' # frag.dump() # this will be a sprite with just the dummy nulltex textures # we need this so that transparent zonewalls in the very old classic zones work # newer zones have actually textured ("collide.dds") zone walls sprite = Sprite(material_name, idx, f30.params1, self.tm) sprite.addTexture('nulltexture', self.nulltex) else: sprite_error = 1 print 'Error in Sprite: could not resolve frag05ref:%i in 0x30 fragment:%i' % ( f30.frag05Ref, f30.id) if sprite_error != 1: # only add error free sprites # sprite.dump() # new style sprite list sprite_list[idx] = sprite if sprite.anim_delay != 0: print("Adding animated sprite to master list " + sprite.name) wld_container.animated_sprites.append(sprite) idx += 1 # need to increment regardless of whether we stored or not # so that the index lookup using the refs in the 0x36's works # preloadWldTextures actually does quite a bit more than just preloading texture files # the main task of this code is to generate our SPRITES # Params # wld_container is a WldContainer object def preloadWldTextures(self, wld_container): self.world.consoleOut('preloading textures for container: ' + wld_container.name) wld = wld_container.wld_file_obj # the in memory wld file # loop over all 0x03 fragments and PRELOAD all referenced texture files from the s3d f31 = None f31_list = [] for f in wld.fragments.values(): if f.type == 0x03: # f.dump() # NOTE # in VERSION 2 WLD zones (ex. povalor, postorms) I've found texture names # that have three parameters prepended like this for example: 1, 4, 0, POVSNOWDET01.DDS # no idea yet as to what these mean but in order to be able to load the texture from # the s3d container we need to strip this stuff for name in f.names: i = name.rfind(',') if i != -1: # See NOTE above print 'parametrized texture name found:%s wld version:0x%x' % ( name, self.wldZone.version) name = name[i + 1:].strip() self.tm.loadTexture(name.lower(), wld_container) # need to store the 0x31 texture lists if f.type == 0x31: f31_list.append(f) # not all wld files define sprites if len(f31_list) == 0: return for f31 in f31_list: self.loadSpriteList(wld_container, f31) #print("Loaded sprites got this many: " + str(len(wld_container.animated_sprites))) # preload the textures/sprites for all loaded containers def preloadTextures(self): # self.preloadWldTextures(self.zone_wld_container) for wld_obj in self.wld_containers.values(): self.preloadWldTextures(wld_obj) # We let Panda3D "flatten" the plethora of GEOMs we created from the original bsp tree # ==> this process creates a complete new NodePath->GeomNode tree from our original # In order to implement texture animation and transparency we need to map the new Geom's textures # back to our Sprites so we can change texture assignments and transparency on a Geom level in # the new structure # Remap the Textures in use for the main Zone Geometry def remapTextures(self): # ------------------------------------------------------------------------------------- # ANIMATED TEXTURE SETUP AND TRANSPARENCY FOR ZONE GEOMETRY (NOT PLACEABLES etc!) # trying to evaluate the scene graph structure under our root node here # since flattenStrong() totally changes the structure of our scene from how we # originally created it, we need to find a way to: # - get a the geoms that the flatten process has produced # - find their textures # - map those back to our sprites # - and finally set up the update process for texture animations based on the above # # NOTE this code will fail if there is more than one sprite useing a single texture! # Not encountered this yet though. self.world.consoleOut('setting up animated textures for zone geometry') for child in self.rootNode.getChildren(): # print child geom_node = child.node() for geom_number in range(0, geom_node.getNumGeoms()): geom_render_state = geom_node.getGeomState(geom_number) attr = geom_render_state.getAttrib( 26 ) # attrib 26 is the texture attribute (hope this is static) if attr != None: # print attr tex = attr.getTexture() # print tex # BINGO! now we have the texture for this GEOM, lets find the sprite sprite = self.zone_wld_container.findSpriteUsing(tex) if sprite != None: # print sprite # set general texture alpha based tansparency for masked textures # if sprite.masked == 1: # child.setTransparency(TransparencyAttrib.MAlpha) if sprite.transparent == 1 or sprite.masked == 1: # EXPERIMENTAL TRANSPARENCY SUPPORT ############### # This is for semi-transparent polygons (water surfaces etc) # we implement the transparency via the alpha component of the GEOM's ColorAttrib ta = TransparencyAttrib.make( TransparencyAttrib.MAlpha) geom_render_state = geom_render_state.setAttrib( ta, 1 ) # potentialy needs passing "int override" (=1?) as second param if not sprite.masked == 1: ca = ColorAttrib.makeFlat( Vec4(1, 1, 1, sprite.alpha)) geom_render_state = geom_render_state.setAttrib( ca, 1 ) # potentialy needs passing "int override" (=1?) as second param geom_node.setGeomState(geom_number, geom_render_state) # ##################################################### if sprite.anim_delay > 0: # ANIMATED SPRITE # sprite.addAnimGeomRenderState((geom_node, geom_number, geom_render_state)) sprite.addAnimGeomRenderState( (geom_node, geom_number, geom_render_state)) else: print 'could not find sprite for geom node, node texture cant be animated' # load up everything related to this zone def load(self): # ---- ZONE GEOMETRY ---- # load main zone s3d s3dfile_name = self.name + '.s3d' self.world.consoleOut('zone loading zone s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir + self.name) if s3d.load() != 0: self.world.consoleOut('ERROR loading s3dfile:' + self.basedir + s3dfile_name) return -1 # s3d.dumpListing() # load main zone wld wldZone = WLDFile(self.name) # wldZone.setDumpList([0x14, 0x15]) wldZone.load(s3d) self.zone_wld_container = WLDContainer('zone', self, wldZone, s3d) self.wld_containers['zone'] = self.zone_wld_container # load the objects.wld file from the same container # this basically consists of 0x15 model references for putting all placeables in place wldZoneObj = WLDFile('objects') # wldZoneObj.setDumpList([0x14, 0x15]) wldZoneObj.load(s3d) self.zone_obj_wld_container = WLDContainer('zone_obj', self, wldZoneObj, s3d) self.wld_containers['zone_obj'] = self.zone_obj_wld_container # ---- placeables definitions ------------------------------------ s3dfile_name = self.name + '_obj.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading placeable objects s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir + self.name + '_obj') if s3d.load() == 0: # s3d.dumpListing() wldObj1 = WLDFile(self.name + '_obj') wldObj1.setDumpList([0x14, 0x13, 0x12, 0x11, 0x10]) wldObj1.load(s3d) self.obj1_wld_container = WLDContainer('obj', self, wldObj1, s3d) self.wld_containers['obj1'] = self.obj1_wld_container else: self.world.consoleOut('zone object 1 s3dfile does not exist:' + self.basedir + s3dfile_name) s3dfile_name = self.name + '_2_obj.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading placeable objects 2 s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir + self.name + '_2_obj') if s3d.load() == 0: # s3d.dumpListing() wldObj2 = WLDFile(self.name + '_2_obj') # wldObj2.setDumpList([0x14, 0x15, 0x2D, 0x36]) wldObj2.load(s3d) self.obj2_wld_container = WLDContainer('obj', self, wldObj2, s3d) self.wld_containers['obj2'] = self.obj2_wld_container else: self.world.consoleOut('zone object 2 s3dfile does not exist:' + self.basedir + s3dfile_name) s3dfile_name = self.name + '_chr.s3d' print '-------------------------------------------------------------------------------------' self.world.consoleOut('zone loading character s3dfile: ' + s3dfile_name) s3d = S3DFile(self.basedir + self.name + '_chr') if s3d.load() == 0: # s3d.dumpListing() wldChr = WLDFile(self.name + '_chr') # wldChr.setDumpList([0x14, 0x15, 0x2D, 0x36]) wldChr.load(s3d) self.chr_wld_container = WLDContainer('chr', self, wldChr, s3d) self.wld_containers['chr'] = self.chr_wld_container else: self.world.consoleOut('zone character s3dfile does not exist:' + self.basedir + s3dfile_name) # --- TEXTURES ---- self.world.consoleOut('preloading textures') self.preloadTextures() # ---- Generate main Zone Geometry ---- self.world.consoleOut('preparing zone mesh') self.prepareZoneMesh() # let Panda3D attempt to flatten the zone geometry (reduce the excessive # Geom count resulting from the layout of the .wld zone data as a huge # bunch of tiny bsp regions) self.world.consoleOut('flattening zone mesh geom tree') # self.rootNode.ls() self.rootNode.flattenStrong() self.rootNode.ls() # texture->sprite remapping after the flatten above self.remapTextures() # COLLISION: # The following makes the complete zone base geometry eligible for collisions # this is of course extremely inefficient. TODO: at some point we need to use the # bsp structures already provided in the wld file to optimize whats in our scene graph # and also to build a more intelligent collision system self.rootNode.setCollideMask(BitMask32.bit(0)) # ---- load MODELS and spawn placeables ----------------------- # go through all the 0x15 refs and create empty model "shells" # for every unique entry self.world.consoleOut('loading placeables models') self.mm.loadPlaceables(wldZoneObj) # self.rootNode.ls() print 'zone load complete' self.load_complete = 1 return 0
def load_into_bamfile(meshdata, subfiles, model): """Uses pycollada and panda3d to load meshdata and subfiles and write out to a bam file on disk""" if os.path.isfile(model.bam_file): print 'returning cached bam file' return model.bam_file mesh = load_mesh(meshdata, subfiles) model_name = model.model_json['full_path'].replace('/', '_') if model.model_type == 'progressive' and model.model_subtype == 'full': progressive_stream = model.model_json['metadata']['types']['progressive'].get('progressive_stream') if progressive_stream is not None: print 'LOADING PROGRESSIVE STREAM' data = model.prog_data try: mesh = add_back_pm.add_back_pm(mesh, StringIO(data), 100) print '-----' print 'SUCCESSFULLY ADDED BACK PM' print '-----' except: f = open(model.bam_file, 'w') f.close() raise print 'loading into bamfile', model_name, mesh scene_members = pandacore.getSceneMembers(mesh) print 'got scene members', model_name, mesh rotateNode = GeomNode("rotater") rotatePath = NodePath(rotateNode) matrix = numpy.identity(4) if mesh.assetInfo.upaxis == collada.asset.UP_AXIS.X_UP: r = collada.scene.RotateTransform(0,1,0,90) matrix = r.matrix elif mesh.assetInfo.upaxis == collada.asset.UP_AXIS.Y_UP: r = collada.scene.RotateTransform(1,0,0,90) matrix = r.matrix rotatePath.setMat(Mat4(*matrix.T.flatten().tolist())) for geom, renderstate, mat4 in scene_members: node = GeomNode("primitive") node.addGeom(geom) if renderstate is not None: node.setGeomState(0, renderstate) geomPath = rotatePath.attachNewNode(node) geomPath.setMat(mat4) print 'created np', model_name, mesh if model.model_type != 'optimized_unflattened' and model.model_type != 'progressive': print 'ABOUT TO FLATTEN' rotatePath.flattenStrong() print 'DONE FLATTENING' print 'flattened', model_name, mesh wrappedNode = pandacore.centerAndScale(rotatePath) wrappedNode.setName(model_name) wrappedNode.writeBamFile(model.bam_file) print 'saved', model_name, mesh wrappedNode = None return model.bam_file
class CogdoMazeFactory: def __init__(self, randomNumGen, width, height, frameWallThickness=Globals.FrameWallThickness, cogdoMazeData=CogdoMazeData): self._rng = RandomNumGen(randomNumGen) self.width = width self.height = height self.frameWallThickness = frameWallThickness self._cogdoMazeData = cogdoMazeData self.quadrantSize = self._cogdoMazeData.QuadrantSize self.cellWidth = self._cogdoMazeData.QuadrantCellWidth def getMazeData(self): if not hasattr(self, '_data'): self._generateMazeData() return self._data def createCogdoMaze(self, flattenModel=True): if not hasattr(self, '_maze'): self._loadAndBuildMazeModel(flatten=flattenModel) return CogdoMaze(self._model, self._data, self.cellWidth) def _gatherQuadrantData(self): self.openBarriers = [] barrierItems = range(Globals.TotalBarriers) self._rng.shuffle(barrierItems) for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]: self.openBarriers.append(i) self.quadrantData = [] quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys() self._rng.shuffle(quadrantKeys) i = 0 for y in xrange(self.height): for x in xrange(self.width): key = quadrantKeys[i] collTable = self._cogdoMazeData.QuadrantCollisions[key] angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)] self.quadrantData.append((key, collTable[angle], angle)) i += 1 if x * y >= self._cogdoMazeData.NumQuadrants: i = 0 def _generateBarrierData(self): data = [] for y in xrange(self.height): data.append([]) for x in xrange(self.width): if x == self.width - 1: ax = -1 else: ax = 1 if y == self.height - 1: ay = -1 else: ay = 1 data[y].append([ax, ay]) dirUp = 0 dirDown = 1 dirLeft = 2 dirRight = 3 def getAvailableDirections(ax, ay, ignore=None): dirs = [] if ax - 1 >= 0 and data[ay][(ax - 1)][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore: dirs.append(dirLeft) if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore: dirs.append(dirRight) if ay - 1 >= 0 and data[(ay - 1)][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore: dirs.append(dirDown) if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore: dirs.append(dirUp) return dirs visited = [] def tryVisitNeighbor(ax, ay, ad): if ad == dirUp: if data[ay][ax] in visited: return None visited.append(data[ay][ax]) data[ay][ax][BARRIER_DATA_TOP] = 0 ay += 1 elif ad == dirDown: if data[(ay - 1)][ax] in visited: return None visited.append(data[(ay - 1)][ax]) data[(ay - 1)][ax][BARRIER_DATA_TOP] = 0 ay -= 1 elif ad == dirLeft: if data[ay][(ax - 1)] in visited: return None visited.append(data[ay][(ax - 1)]) data[ay][(ax - 1)][BARRIER_DATA_RIGHT] = 0 ax -= 1 elif ad == dirRight: if data[ay][ax] in visited: return None visited.append(data[ay][ax]) data[ay][ax][BARRIER_DATA_RIGHT] = 0 ax += 1 return (ax, ay) def openBarriers(x, y): dirs = getAvailableDirections(x, y) for dir in dirs: next = tryVisitNeighbor(x, y, dir) if next is not None: openBarriers(*next) return x = self._rng.randint(0, self.width - 1) y = self._rng.randint(0, self.height - 1) openBarriers(x, y) self._barrierData = data return def _generateMazeData(self): if not hasattr(self, 'quadrantData'): self._gatherQuadrantData() self._data = {} self._data['width'] = (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize self._data['height'] = (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize self._data['originX'] = int(self._data['width'] / 2) self._data['originY'] = int(self._data['height'] / 2) collisionTable = [] horizontalWall = [ 1 for x in xrange(self._data['width']) ] collisionTable.append(horizontalWall) for i in xrange(0, len(self.quadrantData), self.width): for y in xrange(self.quadrantSize): row = [ 1] for x in xrange(i, i + self.width): if x == 1 and y < self.quadrantSize / 2 - 2: newData = [] for j in self.quadrantData[x][1][y]: if j == 0: newData.append(2) else: newData.append(j + 0) row += newData + [1] else: row += self.quadrantData[x][1][y] + [1] collisionTable.append(row) collisionTable.append(horizontalWall[:]) barriers = Globals.MazeBarriers for i in xrange(len(barriers)): for coords in barriers[i]: collisionTable[coords[1]][coords[0]] = 0 y = self._data['originY'] for x in xrange(len(collisionTable[y])): if collisionTable[y][x] == 0: collisionTable[y][x] = 2 x = self._data['originX'] for y in xrange(len(collisionTable)): if collisionTable[y][x] == 0: collisionTable[y][x] = 2 self._data['collisionTable'] = collisionTable def _loadAndBuildMazeModel(self, flatten=False): self.getMazeData() self._model = NodePath('CogdoMazeModel') levelModel = CogdoUtil.loadMazeModel('level') self.quadrants = [] quadrantUnitSize = int(self.quadrantSize * self.cellWidth) frameActualSize = self.frameWallThickness * self.cellWidth size = quadrantUnitSize + frameActualSize halfWidth = int(self.width / 2) halfHeight = int(self.height / 2) i = 0 for y in xrange(self.height): for x in xrange(self.width): ax = (x - halfWidth) * size ay = (y - halfHeight) * size extension = '' if hasattr(getBase(), 'air'): extension = '.bam' filepath = self.quadrantData[i][0] + extension angle = self.quadrantData[i][2] m = self._createQuadrant(filepath, i, angle, quadrantUnitSize) m.setPos(ax, ay, 0) m.reparentTo(self._model) self.quadrants.append(m) i += 1 quadrantHalfUnitSize = quadrantUnitSize * 0.5 barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider') y = 3 for x in xrange(self.width): if x == (self.width - 1) / 2: continue ax = (x - halfWidth) * size ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5) b = NodePath('barrier') barrierModel.instanceTo(b) b.setPos(ax, ay, 0) b.reparentTo(self._model) offset = self.cellWidth - 0.5 for x in (0, 3): for y in xrange(self.height): ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset ay = (y - halfHeight) * size b = NodePath('barrier') barrierModel.instanceTo(b) b.setPos(ax, ay, 0) b.setH(90) b.reparentTo(self._model) offset -= 2.0 barrierModel.removeNode() levelModel.getChildren().reparentTo(self._model) for np in self._model.findAllMatches('**/*lightCone*'): CogdoUtil.initializeLightCone(np, 'fixed', 3) if flatten: self._model.flattenStrong() return self._model def _createQuadrant(self, filepath, serialNum, angle, size): root = NodePath('QuadrantRoot-%i' % serialNum) quadrant = loader.loadModel(filepath) quadrant.getChildren().reparentTo(root) root.setH(angle) return root
class CharSelection(DirectObject): notify = directNotify.newCategory('CharSelection') NO_TOON = "Empty Slot" PLAY = "Play" CREATE = "Create" TITLE = "Pick A Toon To Play" def __init__(self, avChooser): self.avChooser = avChooser self.choice = None self.charList = None self.charNameLabel = None self.charButtons = [] self.playOrCreateButton = None self.deleteButton = None self.quitButton = None self.title = None self.stageToon = None self.stageToonRoot = None self.deleteConf = None self.frame = None self.stageFSM = ClassicFSM.ClassicFSM('StageFSM', [ State.State('off', self.enterOff, self.exitOff), State.State('loadSZ', self.enterLoadSZ, self.exitLoadSZ), State.State('onStage', self.enterOnStage, self.exitOnStage) ], 'off', 'off') self.stageFSM.enterInitialState() self.selectionFSM = ClassicFSM.ClassicFSM('CharSelection', [ State.State('off', self.enterOff, self.exitOff), State.State('character', self.enterCharSelected, self.exitCharSelected), State.State('empty', self.enterEmptySelected, self.exitEmptySelected) ], 'off', 'off') self.selectionFSM.enterInitialState() self.szGeom = None self.olc = None self.asyncSZLoadStatus = False self.isNewToon = False self.newToonSlot = None self.camIval = None self.stAnimSeq = None self.newToonAnnounceSfx = base.loadSfx( "phase_4/audio/sfx/King_Crab.ogg") self.newToonDrumrollSfx = base.loadSfx( "phase_5/audio/sfx/SZ_MM_drumroll.ogg") self.newToonRevealSfx = base.loadSfx( "phase_5/audio/sfx/SZ_MM_fanfare.ogg") self.dnaStore = DNAStorage() loader.loadDNAFile(self.dnaStore, 'phase_4/dna/pickatoon/storage_pickatoon.pdna') def __setupStageToon(self): self.stageToonRoot = render.attachNewNode('stageToonRoot') self.stageToon = Toon(base.cr) self.stageToon.setPosHpr(0, 0, 0, 0, 0, 0) self.stageToon.reparentTo(self.stageToonRoot) def cleanupStageToon(self): if self.stageToon != None: self.stageToon.disable() self.stageToon.delete() self.stageToon = None if self.stageToonRoot != None: self.stageToonRoot.removeNode() self.stageToonRoot = None def enterOff(self): pass def exitOff(self): pass def __async_loadSZTask(self, task=None): dnas = HOOD_ID_2_DNA[self.choice.lastHood] for i in xrange(len(dnas)): dnaFile = dnas[i] if i == len(dnas) - 1: node = loader.loadDNAFile(self.dnaStore, dnaFile) if node.getNumParents() == 1: self.szGeom = NodePath(node.getParent(0)) self.szGeom.reparentTo(render) else: self.szGeom = render.attachNewNode(node) # The 2D trees should not be flattened, to do that, we're going to traverse # the scene graph for all trees, use #wrtReparentTo(render) on them, flatten # the scene with #flattenStrong(), and finally #wrtReparentTo(self.szGeom) # the trees back to the main scene node so they get cleaned up properly. trees = self.szGeom.findAllMatches('**/*tree*') #self.szGeom.find("**/shadow").removeNode() #from panda3d.core import CullBinAttrib #self.szGeom.find("**/shadow_crack").setAttrib(CullBinAttrib.make("foreground", 10), 10) #shs = self.szGeom.findAllMatches("**/*shadow*") #for sh in shs: # sh.removeNode() # self.szGeom.ls() for tree in trees: tree.wrtReparentTo(render) self.szGeom.flattenStrong() for tree in trees: tree.wrtReparentTo(self.szGeom) else: loader.loadDNAFile(self.dnaStore, dnaFile) self.olc = ZoneUtil.getOutdoorLightingConfig(self.choice.lastHood) self.olc.setup() self.olc.apply() CIGlobals.preRenderScene(render) self.asyncSZLoadStatus = True #base.accept('l', render.ls) if task: return task.done def enterLoadSZ(self): self.loadingDlg = Dialog.GlobalDialog("Loading...") self.loadingDlg.show() base.cr.renderFrame() base.cr.renderFrame() self.notify.info("Polling for SZ to load") self.asyncSZLoadStatus = False self.__async_loadSZTask() base.doNextFrame(self.stageFSM.request, ['onStage']) def exitLoadSZ(self): if hasattr(self, 'loadingDlg'): self.loadingDlg.cleanup() del self.loadingDlg def __changeCamFOV(self, val): base.camLens.setMinFov(val / (4. / 3.)) def enterOnStage(self): dna = self.choice.dna name = self.choice.name self.stageToon.setName(name) self.stageToon.setDNAStrand(dna) self.stageToon.nametag.setNametagColor( NametagGlobals.NametagColors[NametagGlobals.CCLocal]) self.stageToon.nametag.setActive(0) self.stageToon.nametag.nametag3d.request('Rollover') self.stageToon.nametag.unmanage(base.marginManager) self.stageToon.nametag.updateAll() self.stageToon.animFSM.request('neutral') self.stageToon.setPosHpr(0, 0, 0, 10, 0, 0) self.stageToon.show() dat = HOOD_STAGE_DATA[self.choice.lastHood] self.stageToonRoot.setPos(dat[2]) self.stageToonRoot.setHpr(dat[3]) camera.reparentTo(self.stageToonRoot) camera.setPos(dat[0]) camera.lookAt(self.stageToonRoot, 0, 0, 3) startHpr = camera.getHpr() camera.setPos(dat[1]) camera.lookAt(self.stageToonRoot, 0, 0, 3) endHpr = camera.getHpr() self.camIval = Parallel( LerpPosInterval(camera, 5.0, dat[1] - (1.6, 0, 0), dat[0] - (1.6, 0, 0), blendType='easeInOut'), LerpQuatInterval(camera, 5.0, hpr=endHpr, startHpr=startHpr, blendType='easeInOut'), LerpFunc(self.__changeCamFOV, duration=5.0, fromData=80.0, toData=CIGlobals.DefaultCameraFov, blendType='easeInOut')) if self.isNewToon: self.camIval.append( Sequence( Func(self.stageToon.hide), Func(base.stopMusic), SoundInterval(self.newToonAnnounceSfx, startTime=1.674, duration=4.047), SoundInterval(self.newToonDrumrollSfx), Func(self.stageToon.pose, 'tele', self.stageToon.getNumFrames('tele')), Func(self.newToonAppear), Func(self.stageToon.show), SoundInterval(self.newToonRevealSfx), Func(base.cr.playTheme))) else: self.camIval.append( Sequence(Func(self.showActionButtons), Func(self.enableAllCharButtons), Wait(5.0), Func(self.beginRandomAnims))) self.camIval.start() def hideActionButtons(self): self.playOrCreateButton.hide() self.deleteButton.hide() def showActionButtons(self): self.playOrCreateButton.show() self.deleteButton.show() def newToonAppear(self): self.stopSTAnimSeq() self.stAnimSeq = Sequence( Func(self.stageToon.animFSM.request, 'teleportIn'), Wait(2.0), ActorInterval(self.stageToon, 'wave'), Func(self.stageToon.loop, 'neutral'), Func(self.beginRandomAnims), Func(self.enableAllCharButtons), Func(self.showActionButtons)) self.stAnimSeq.start() def stopSTAnimSeq(self): if self.stAnimSeq: self.stAnimSeq.finish() self.stAnimSeq = None def unloadSZGeom(self): if self.szGeom: self.szGeom.removeNode() self.szGeom = None if self.olc: self.olc.cleanup() self.olc = None def beginRandomAnims(self): self.stageToon.startLookAround() taskMgr.doMethodLater(random.uniform(*ST_ANIM_IVAL), self.__doRandomSTAnim, "doRandomSTAnim") def __doRandomSTAnim(self, task): anim = random.choice(ST_RANDOM_ANIMS) self.stopSTAnimSeq() self.stageToon.stopLookAround() head = self.stageToon.getPart('head') if anim == 'read': self.stAnimSeq = Sequence( Func(self.stageToon.lerpLookAt, head, (0, -15, 0)), Func(self.stageToon.animFSM.request, 'openBook'), Wait(0.5), Func(self.stageToon.animFSM.request, 'readBook'), Wait(2.0), Func(self.stageToon.lerpLookAt, head, (0, 0, 0)), Func(self.stageToon.animFSM.request, 'closeBook'), Wait(1.75), Func(self.stageToon.loop, 'neutral'), Func(self.stageToon.startLookAround)) else: self.stageToon.lerpLookAt(head, (0, 0, 0)) self.stAnimSeq = Sequence(ActorInterval(self.stageToon, anim), Func(self.stageToon.loop, 'neutral'), Func(self.stageToon.startLookAround)) self.stAnimSeq.start() task.delayTime = random.uniform(*ST_ANIM_IVAL) return task.again def endRandomAnims(self): taskMgr.remove("doRandomSTAnim") self.stopSTAnimSeq() def exitOnStage(self): self.isNewToon = False if self.camIval: self.camIval.finish() self.camIval = None self.endRandomAnims() self.stopSTAnimSeq() camera.reparentTo(render) camera.setPosHpr(0, 0, 0, 0, 0, 0) #base.transitions.fadeScreen(1.0) self.unloadSZGeom() self.stageToon.hide() def enterCharSelected(self): self.playOrCreateButton['text'] = self.PLAY self.playOrCreateButton['extraArgs'] = ['play'] #aspect2d.hide() def exitCharSelected(self): self.playOrCreateButton.hide() self.deleteButton.hide() def enterEmptySelected(self): self.charNameLabel.setText(self.NO_TOON) self.playOrCreateButton['text'] = self.CREATE self.playOrCreateButton['extraArgs'] = ['create'] self.playOrCreateButton.show() def exitEmptySelected(self): self.playOrCreateButton.hide() def __action(self, action): for btn in self.charButtons: if btn['state'] == DGG.DISABLED: self.slot = btn.getPythonTag('slot') break func = None arg = None doFade = True if action == 'delete': func = self.deleteToon arg = self.choice.avId doFade = False elif action == 'play': func = self.playGame arg = self.choice.slot elif action == 'create': func = self.enterMAT elif action == 'quit': func = sys.exit doFade = False if doFade: base.transitions.fadeOut(0.3) if arg != None: Sequence(Wait(0.31), Func(func, arg)).start() else: Sequence(Wait(0.31), Func(func)).start() else: if arg != None: func(arg) else: func() def playGame(self, slot): messenger.send("avChooseDone", [self.avChooser.getAvChoiceBySlot(slot)]) def enterMAT(self): messenger.send("enterMakeAToon", [self.slot]) def deleteToon(self, avId): # Show a confirmation message self.deleteConf = Dialog.GlobalDialog( message='This will delete {0} forever.\n\nAre you sure?'.format( self.avChooser.getNameFromAvId(avId)), style=Dialog.YesNo, doneEvent='deleteConfResponse', extraArgs=[avId]) self.acceptOnce('deleteConfResponse', self.__handleDeleteConfResponse) self.deleteConf.show() def __handleDeleteConfResponse(self, avId): doneStatus = self.deleteConf.getValue() if doneStatus: # Alright, they pressed yes. No complaining to us. self.avChooser.avChooseFSM.request("waitForToonDelResponse", [avId]) else: self.deleteConf.cleanup() self.deleteConf = None def __handleCharButton(self, slot): for btn in self.charButtons: if btn.getPythonTag('slot') == slot: btn['state'] = DGG.DISABLED else: btn['state'] = DGG.NORMAL if self.avChooser.hasToonInSlot(slot): self.choice = self.avChooser.getAvChoiceBySlot(slot) self.selectionFSM.request('character') self.stageFSM.request('loadSZ') else: self.selectionFSM.request('empty') self.stageFSM.request('off') def disableAllCharButtons(self): for btn in self.charButtons: btn['state'] = DGG.DISABLED def enableAllCharButtons(self): for btn in self.charButtons: if not self.choice or btn.getPythonTag('slot') != self.choice.slot: btn['state'] = DGG.NORMAL def load(self, newToonSlot=None): self.isNewToon = newToonSlot is not None self.newToonSlot = newToonSlot base.transitions.noTransitions() base.cr.renderFrame() base.camLens.setMinFov(CIGlobals.DefaultCameraFov / (4. / 3.)) self.__setupStageToon() self.title = DirectLabel(text=self.TITLE, text_font=CIGlobals.getMickeyFont(), text_fg=(1, 0.9, 0.1, 1), relief=None, text_scale=0.13, pos=(0, 0, 0.82)) self.charNameLabel = OnscreenText(text="", font=CIGlobals.getMickeyFont(), pos=(-0.25, 0.5, 0), fg=(1, 0.9, 0.1, 1.0)) self.charNameLabel.hide() self.frame = DirectFrame() self.frame['image'] = DGG.getDefaultDialogGeom() self.frame['image_color'] = CIGlobals.DialogColor self.frame['image_scale'] = (-0.9, -0.9, 0.8) self.frame['image_pos'] = (0.82, 0, -0.125) self.playOrCreateButton = DirectButton( text="", pos=(0.8125, 0, -0.35), command=self.__action, geom=CIGlobals.getDefaultBtnGeom(), text_scale=0.06, relief=None, text_pos=(0, -0.01)) self.playOrCreateButton.hide() self.deleteButton = DirectButton(text="Delete", pos=(0.8125, 0, -0.45), command=self.__action, extraArgs=['delete'], geom=CIGlobals.getDefaultBtnGeom(), text_scale=0.06, relief=None, text_pos=(0, -0.01)) self.deleteButton.hide() self.quitButton = DirectButton(text="Quit", pos=(-1.10, 0, -0.925), command=self.__action, extraArgs=['quit'], text_scale=0.06, geom=CIGlobals.getDefaultBtnGeom(), relief=None, text_pos=(0, -0.01)) for slot in range(6): if self.avChooser.hasToonInSlot(slot): choice = self.avChooser.getAvChoiceBySlot(slot) text = choice.name else: text = self.NO_TOON btn = CIGlobals.makeDefaultScrolledListBtn( text=text, text_scale=0.06, command=self.__handleCharButton, extraArgs=[slot]) btn.setPythonTag('slot', slot) self.charButtons.append(btn) btn['state'] = DGG.NORMAL self.charList = CIGlobals.makeDefaultScrolledList( pos=(0.75, 0, -0.225), listZorigin=-0.43, listFrameSizeZ=0.51, arrowButtonScale=0.0, items=self.charButtons, parent=self.frame) if self.isNewToon: self.__handleCharButton(self.newToonSlot) self.disableAllCharButtons() def unload(self): self.selectionFSM.requestFinalState() self.stageFSM.requestFinalState() self.cleanupStageToon() self.choice = None if self.frame: self.frame.destroy() self.frame = None if self.charButtons: for btn in self.charButtons: btn.destroy() self.charButtons = None if self.deleteConf: self.deleteConf.cleanup() self.deleteConf = None if self.charList: self.charList.destroy() self.charList = None if self.charNameLabel: self.charNameLabel.destroy() self.charNameLabel = None if self.playOrCreateButton: self.playOrCreateButton.destroy() self.playOrCreateButton = None if self.deleteButton: self.deleteButton.destroy() self.deleteButton = None if self.quitButton: self.quitButton.destroy() self.quitButton = None if self.title: self.title.destroy() self.title = None base.camera.setPos(0, 0, 0) base.camera.setHpr(0, 0, 0) base.transitions.noTransitions() del self.selectionFSM
class CogdoMazeFactory: def __init__(self, randomNumGen, width, height, frameWallThickness = Globals.FrameWallThickness, cogdoMazeData = CogdoMazeData): self._rng = RandomNumGen(randomNumGen) self.width = width self.height = height self.frameWallThickness = frameWallThickness self._cogdoMazeData = cogdoMazeData self.quadrantSize = self._cogdoMazeData.QuadrantSize self.cellWidth = self._cogdoMazeData.QuadrantCellWidth def getMazeData(self): if not hasattr(self, '_data'): self._generateMazeData() return self._data def createCogdoMaze(self, flattenModel = True): if not hasattr(self, '_maze'): self._loadAndBuildMazeModel(flatten=flattenModel) return CogdoMaze(self._model, self._data, self.cellWidth) def _gatherQuadrantData(self): self.openBarriers = [] barrierItems = range(Globals.TotalBarriers) self._rng.shuffle(barrierItems) for i in barrierItems[0:len(barrierItems) - Globals.NumBarriers]: self.openBarriers.append(i) self.quadrantData = [] quadrantKeys = self._cogdoMazeData.QuadrantCollisions.keys() self._rng.shuffle(quadrantKeys) i = 0 for y in xrange(self.height): for x in xrange(self.width): key = quadrantKeys[i] collTable = self._cogdoMazeData.QuadrantCollisions[key] angle = self._cogdoMazeData.QuadrantAngles[self._rng.randint(0, len(self._cogdoMazeData.QuadrantAngles) - 1)] self.quadrantData.append((key, collTable[angle], angle)) i += 1 if x * y >= self._cogdoMazeData.NumQuadrants: i = 0 def _generateBarrierData(self): data = [] for y in xrange(self.height): data.append([]) for x in xrange(self.width): if x == self.width - 1: ax = -1 else: ax = 1 if y == self.height - 1: ay = -1 else: ay = 1 data[y].append([ax, ay]) dirUp = 0 dirDown = 1 dirLeft = 2 dirRight = 3 def getAvailableDirections(ax, ay, ignore = None): dirs = [] if ax - 1 >= 0 and data[ay][ax - 1][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore: dirs.append(dirLeft) if ax + 1 < self.width and data[ay][ax][BARRIER_DATA_RIGHT] == 1 and (ax, ay) != ignore: dirs.append(dirRight) if ay - 1 >= 0 and data[ay - 1][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore: dirs.append(dirDown) if ay + 1 < self.height and data[ay][ax][BARRIER_DATA_TOP] == 1 and (ax, ay) != ignore: dirs.append(dirUp) return dirs visited = [] def tryVisitNeighbor(ax, ay, ad): if ad == dirUp: if data[ay][ax] in visited: return None visited.append(data[ay][ax]) data[ay][ax][BARRIER_DATA_TOP] = 0 ay += 1 elif ad == dirDown: if data[ay - 1][ax] in visited: return None visited.append(data[ay - 1][ax]) data[ay - 1][ax][BARRIER_DATA_TOP] = 0 ay -= 1 elif ad == dirLeft: if data[ay][ax - 1] in visited: return None visited.append(data[ay][ax - 1]) data[ay][ax - 1][BARRIER_DATA_RIGHT] = 0 ax -= 1 elif ad == dirRight: if data[ay][ax] in visited: return None visited.append(data[ay][ax]) data[ay][ax][BARRIER_DATA_RIGHT] = 0 ax += 1 return (ax, ay) def openBarriers(x, y): dirs = getAvailableDirections(x, y) for dir in dirs: next = tryVisitNeighbor(x, y, dir) if next is not None: openBarriers(*next) return x = self._rng.randint(0, self.width - 1) y = self._rng.randint(0, self.height - 1) openBarriers(x, y) self._barrierData = data return def _generateMazeData(self): if not hasattr(self, 'quadrantData'): self._gatherQuadrantData() self._data = {} self._data['width'] = (self.width + 1) * self.frameWallThickness + self.width * self.quadrantSize self._data['height'] = (self.height + 1) * self.frameWallThickness + self.height * self.quadrantSize self._data['originX'] = int(self._data['width'] / 2) self._data['originY'] = int(self._data['height'] / 2) collisionTable = [] horizontalWall = [ 1 for x in xrange(self._data['width']) ] collisionTable.append(horizontalWall) for i in xrange(0, len(self.quadrantData), self.width): for y in xrange(self.quadrantSize): row = [1] for x in xrange(i, i + self.width): if x == 1 and y < self.quadrantSize / 2 - 2: newData = [] for j in self.quadrantData[x][1][y]: if j == 0: newData.append(2) else: newData.append(j + 0) row += newData + [1] else: row += self.quadrantData[x][1][y] + [1] collisionTable.append(row) collisionTable.append(horizontalWall[:]) barriers = Globals.MazeBarriers for i in xrange(len(barriers)): for coords in barriers[i]: collisionTable[coords[1]][coords[0]] = 0 y = self._data['originY'] for x in xrange(len(collisionTable[y])): if collisionTable[y][x] == 0: collisionTable[y][x] = 2 x = self._data['originX'] for y in xrange(len(collisionTable)): if collisionTable[y][x] == 0: collisionTable[y][x] = 2 self._data['collisionTable'] = collisionTable def _loadAndBuildMazeModel(self, flatten = False): self.getMazeData() self._model = NodePath('CogdoMazeModel') levelModel = CogdoUtil.loadMazeModel('level') self.quadrants = [] quadrantUnitSize = int(self.quadrantSize * self.cellWidth) frameActualSize = self.frameWallThickness * self.cellWidth size = quadrantUnitSize + frameActualSize halfWidth = int(self.width / 2) halfHeight = int(self.height / 2) i = 0 for y in xrange(self.height): for x in xrange(self.width): ax = (x - halfWidth) * size ay = (y - halfHeight) * size extension = '' if hasattr(getBase(), 'air'): extension = '.bam' filepath = self.quadrantData[i][0] + extension angle = self.quadrantData[i][2] m = self._createQuadrant(filepath, i, angle, quadrantUnitSize) m.setPos(ax, ay, 0) m.reparentTo(self._model) self.quadrants.append(m) i += 1 quadrantHalfUnitSize = quadrantUnitSize * 0.5 barrierModel = CogdoUtil.loadMazeModel('grouping_blockerDivider').find('**/divider') y = 3 for x in xrange(self.width): if x == (self.width - 1) / 2: continue ax = (x - halfWidth) * size ay = (y - halfHeight) * size - quadrantHalfUnitSize - (self.cellWidth - 0.5) b = NodePath('barrier') barrierModel.instanceTo(b) b.setPos(ax, ay, 0) b.reparentTo(self._model) offset = self.cellWidth - 0.5 for x in (0, 3): for y in xrange(self.height): ax = (x - halfWidth) * size - quadrantHalfUnitSize - frameActualSize + offset ay = (y - halfHeight) * size b = NodePath('barrier') barrierModel.instanceTo(b) b.setPos(ax, ay, 0) b.setH(90) b.reparentTo(self._model) offset -= 2.0 barrierModel.removeNode() levelModel.getChildren().reparentTo(self._model) for np in self._model.findAllMatches('**/*lightCone*'): CogdoUtil.initializeLightCone(np, 'fixed', 3) if flatten: self._model.flattenStrong() return self._model def _createQuadrant(self, filepath, serialNum, angle, size): root = NodePath('QuadrantRoot-%i' % serialNum) quadrant = loader.loadModel(filepath) quadrant.getChildren().reparentTo(root) root.setH(angle) return root
class TerrainRenderer: """Render the map.""" def __init__(self): """Init the class.""" self.terrain_map = [[]] self.geom_node = NodePath('terrain') self.minimap_node = NodePath('minimap') self.coll_node = CollisionNode('cnode') self.geom_builder = GeomBuilder('floor') def get_tile(self, i, j): """Get element from terrain_map.""" try: return self.terrain_map[i][j] except IndexError: return Empty() def create_geom(self, loader): """Creates self.geom_node from self.terrain_map.""" # geom_builder = GeomBuilder('floor') map_size = len(self.terrain_map) unit_size = map_params.unit_size start_pos = -map_size*unit_size/2 # colors = map_params.colors # geom_builder.add_rect( # colors.floor, # start_pos, start_pos, 0, # -start_pos, -start_pos, 0 # ) card_maker = CardMaker("cm") card_maker.setFrame( Point3(-start_pos, -start_pos, 0), Point3(+start_pos, -start_pos, 0), Point3(+start_pos, +start_pos, 0), Point3(-start_pos, +start_pos, 0) ) card_maker.setColor(map_params.colors.floor) floor_node = NodePath(card_maker.generate()) floor_node.reparentTo(self.geom_node) # floor_node.setHpr(0, 90, 0) # floor_node.setPos(0, 0, 0) tex = loader.loadTexture('models/floor.png') floor_node.setTexture(tex, 1) floor_node.setTexScale(TextureStage.getDefault(), map_size, map_size) def get(i, j): return isinstance(self.get_tile(i, j), Wall) wall_count = 0 for i in range(map_size-1): for j in range(map_size-1): if any([get(i, j), get(i+1, j), get(i+1, j+1), get(i, j+1)]): wall_count += 1 def callback(): self.geom_node.clearModelNodes() self.geom_node.flattenStrong() threads = Threads(wall_count, callback) for i in range(map_size-1): for j in range(map_size-1): current_position = ( start_pos+i*unit_size, start_pos+j*unit_size, 0 ) render_wall( current_position, [get(i, j), get(i+1, j), get(i+1, j+1), get(i, j+1)], ((i+j) & 1)+1, self.geom_node, loader, threads ) def create_collision(self): """Creates self.coll_node from self.terrain_map.""" map_size = len(self.terrain_map) start_pos = -map_size*map_params.unit_size/2 box_size = Vec3( map_params.unit_size, map_params.unit_size, map_params.height ) for i in range(map_size): for j in range(map_size): current_position = Point3( start_pos+i*map_params.unit_size, start_pos+j*map_params.unit_size, 0 ) if isinstance(self.get_tile(i, j), Wall): self.coll_node.addSolid(CollisionBox( current_position, current_position + box_size )) def create_minimap(self): """Creates self.minimap_node from self.terrain_map.""" map_size = len(self.terrain_map) for i in range(map_size): for j in range(map_size): self.get_tile(i, j).generate_map((i, j)).reparentTo( self.minimap_node)