def verticalGroupFinish(grp, test=False): grpN = NodePath(grp) widest = getWidest(grpN) # get prelim size vertPos = 0 for i in grpN.getChildren(): # finish siblings c = i.node().getPythonTag('extras') #if not test: c.finish(pos=(0, 0, -vertPos), minWidth=widest) vertPos += c.getFHeight() # finished height widest = getWidest(grpN) # get finished size for i in grpN.getChildren(): # finish siblings cnp = NodePath(i).find('-PandaNode') if cnp: cnp.setX(widest) grpN.hide()
def verticalGroupFinish(grp, test=False): grpN = NodePath(grp) widest = getWidest(grpN) # get prelim size vertPos=0 for i in grpN.getChildren(): # finish siblings c = i.node().getPythonTag('extras') #if not test: c.finish(pos=(0,0,-vertPos), minWidth=widest) vertPos+=c.getFHeight()# finished height widest = getWidest(grpN) # get finished size for i in grpN.getChildren(): # finish siblings cnp = NodePath(i).find('-PandaNode') if cnp: cnp.setX(widest) grpN.hide()
def __reverseItems(self): tmp = NodePath('') self.itemsParent.getChildren().reparentTo(tmp) children = asList(tmp.getChildren()) for c in reversed(children): c.reparentTo(self.itemsParent) tmp.removeNode() x = self.sidePad * self.scale[0] + self.gapFromEdge for c in asList(self.itemsParent.getChildren()): c.setX(x) fr = c.node().getFrame() x += (fr[1] - fr[0]) * self.scale[0]
def addItems(self, items, nextToID, after=True): nextToPath = self.np.find('**/=id='+nextToID) sybGroup = NodePath(nextToPath.node().getParent(0)) menuItms = self.makeMenu('tmp', indent2tree(items), sybGroup.node()) sybs = sybGroup.getChildren() sybGroup.node().removeAllChildren() for s in sybs: if s.node().hasPythonTag('extras'): s.node().getPythonTag('extras').deformat() if s == nextToPath: if after: s.reparentTo(sybGroup) for m in NodePath(menuItms).getChildren(): m.getPythonTag('extras').deformat() m.reparentTo(sybGroup) else: for c in NodePath(menuItms).getChildren(): c.getPythonTag('extras').deformat() c.reparentTo(sybGroup) s.reparentTo(sybGroup) else: sybGroup.node().addChild(s.node()) verticalGroupFinish(sybGroup.node())
def addItems(self, items, nextToID, after=True): nextToPath = self.np.find('**/=id=' + nextToID) sybGroup = NodePath(nextToPath.node().getParent(0)) menuItms = self.makeMenu('tmp', indent2tree(items), sybGroup.node()) sybs = sybGroup.getChildren() sybGroup.node().removeAllChildren() for s in sybs: if s.node().hasPythonTag('extras'): s.node().getPythonTag('extras').deformat() if s == nextToPath: if after: s.reparentTo(sybGroup) for m in NodePath(menuItms).getChildren(): m.getPythonTag('extras').deformat() m.reparentTo(sybGroup) else: for c in NodePath(menuItms).getChildren(): c.getPythonTag('extras').deformat() c.reparentTo(sybGroup) s.reparentTo(sybGroup) else: sybGroup.node().addChild(s.node()) verticalGroupFinish(sybGroup.node())
def destroy(self): """Destroy all assets and removes the root node. This makes an instance of AssetManager unusable!""" for a in self.assets: a.destroy() self.assets = [] self.root = None def __repr__(self): r="AssetManager for root {}:".format(self.root) for a in self.assets: r+="\n\t{} {}".format(type(a).__name__, a.name) return r # Test # Needs assets.empty.Empty if __name__ == "__main__": from panda3d.core import NodePath root = NodePath("root") am = AssetManager(root) id0 = am.add("Empty", "empty test asset 1") id1 = am.add("Empty", "empty test asset 2") assert id0 == 0 and id1 == 1 assert am.getById(id1).__class__.__name__ == "Empty" assert am.getById(id0) == am.getByName("empty test asset 1")[0] am.destroy() assert len(am.assets) == 0 assert root.getChildren().getNumPaths() == 0
class RigidBodyRig: def __init__(self): self.root = NodePath('root') def createColliders(self, pose_rig): self.colliders = list(create_colliders(self.root, pose_rig, joints_config)) def createConstraints(self, offset_scale): self.constraints = list(create_constraints(self.root, get_joint_pairs(self.root, joints_config), offset_scale)) def clearMasses(self): [collider.node().setMass(0) for collider in self.colliders] def attachCubes(self, loader): for collider in self.colliders: for i in range(collider.node().getNumShapes()): shape = collider.node().getShape(i) mat = collider.node().getShapeMat(i) scale = shape.getHalfExtentsWithMargin() transform = TransformState.makeMat(mat) cube = loader.loadModel("cube.egg") cube.setTransform(transform) cube.setScale(scale) cube.reparentTo(collider) def setScale(self, *scale): self.root.setScale(*scale) def setPos(self, *pos): self.root.setPos(*pos) def babble(self): F_all = [] for collider in self.colliders: collider_name = collider.getName() if collider_name == "Hips": continue F = random.uniform(-1.0, 1.0) * joints_config[collider_name].get("F_max", 4000.0) F_all.append(F) r = Vec3(*joints_config[collider_name].get("axis", (1, 0, 0))) r_world = collider.getQuat(self.root).xform(r) T = r_world * F collider.node().applyTorqueImpulse(T) return np.array(F_all) def apply_forces(self, F_all): i = 0 for collider in self.colliders: if collider.getName() == "Hips": continue F = F_all[i] r = Vec3(1, 0, 0) r_world = collider.getQuat(self.root).xform(r) T = r_world * F collider.node().applyTorqueImpulse(T) i += 1 def compareTo(self, target): positions = self.getJointPositions() rotations = self.getJointRotations() target_positions = target.getJointPositions() target_rotations = target.getJointRotations() err_pos = np.abs((positions - target_positions) / 200.0).sum() err_hpr = np.abs((rotations - target_rotations) / 180.0).sum() return err_pos + err_hpr def matchPose(self, pose_rig): for node, parent in pose_rig.exposed_joints: if node.getName() not in joints_config: continue joint_config = joints_config[node.getName()] if 'joints' not in joint_config: continue joints = joint_config['joints'] if len(joints) == 0: continue box_np = self.root.find(node.getName()) if len(joints) > 1: pos = node.getPos(pose_rig.actor) hpr = node.getHpr(pose_rig.actor) box_np.setPosHpr(self.root, pos, hpr) else: joint = joints.keys()[0] child_node, child_parent = next((child_node, child_parent) for child_node, child_parent in pose_rig.exposed_joints if child_node.getName() == joint) box_np.setPos(child_parent, child_node.getPos(child_parent) / 2.0) quat = Quat() lookAt(quat, child_node.getPos(child_parent), Vec3.up()) box_np.setQuat(child_parent, quat) box_np.node().clearForces() def clearForces(self): for collider in self.colliders: collider.node().clearForces() def setPos(self, *pos): self.root.setPos(*pos) def reparentTo(self, other): self.root.reparentTo(other) def setCollideMask(self, mask): for child in self.root.getChildren(): child.setCollideMask(mask) def attachRigidBodies(self, world): for collider in self.colliders: world.attachRigidBody(collider.node()) def attachConstraints(self, world): for constraint in self.constraints: world.attachConstraint(constraint, linked_collision=True) def getJointPositions(self): return np.concatenate([collider.getPos(self.root) for collider in self.colliders]) # TODO: is root necessary? def getJointRotations(self): return np.concatenate([collider.getHpr(self.root) for collider in self.colliders]) # TODO: is root necessary? def getLinearVelocities(self): return np.concatenate([collider.node().getLinearVelocity() for collider in self.colliders]) def getAngularVelocities(self): return get_angle_vec(np.concatenate([collider.node().getAngularVelocity() for collider in self.colliders]))
class Transform(Component): """Each game object has exactly one of these. A transform holds data about position, rotation, scale and parent relationship. In Panity this is a wrapper for a NodePath. """ def __init__(self, game_object, name): # Component class sets self.game_object = game_object Component.__init__(self, game_object) self.node = NodePath(name) self.node.setPythonTag("transform", self) @classmethod def getClassSerializedProperties(cls): """Return all special property attributes in a dict. Only attributes derived from SerializedProperty are respected. On transform component this method always returns local position, -rotation and scale only. """ d = {} d["local_position"] = Transform.local_position d["local_euler_angles"] = Transform.local_euler_angles d["local_scale"] = Transform.local_scale return d def getSerializedProperties(self): """Return all properties for serialization. In the case of transform this only returns local position, -rotation and -scale, which are required to restore the state of the node. """ d = {} d["local_position"] = self.local_position d["local_euler_angles"] = self.local_euler_angles d["local_scale"] = self.local_scale return d # We use the panda node to save the name on it for better debugging and # efficient finding of nodes with NodePath().find() @SerializedPropertyDecorator def name(self): return self.node.getName() @name.setter def name(self, name): if name == "render": name = "_render" self.node.setName(name) @SerializedPropertyDecorator def position(self): return self.node.getPos(self.root.node) @position.setter def position(self, position): self.node.setPos(self.root.node, *position) @SerializedPropertyDecorator def local_position(self): return self.node.getPos() @local_position.setter def local_position(self, position): self.node.setPos(*position) @SerializedPropertyDecorator def euler_angles(self): return self.node.getHpr(self.root.node) @euler_angles.setter def euler_angles(self, angles): self.node.setHpr(self.root.node, *angles) @SerializedPropertyDecorator def local_euler_angles(self): return self.node.getHpr() @local_euler_angles.setter def local_euler_angles(self, angles): self.node.setHpr(*angles) @SerializedPropertyDecorator def rotation(self): return self.node.getQuat(self.root.node) @rotation.setter def rotation(self, quaternion): self.node.setQuat(self.root.node, *quaternion) @SerializedPropertyDecorator def local_rotation(self): return self.node.getQuat() @local_rotation.setter def local_rotation(self, quaternion): self.node.setQuat(*quaternion) @SerializedPropertyDecorator def local_scale(self): return self.node.getScale() @local_scale.setter def local_scale(self, scale): self.node.setScale(*scale) @SerializedPropertyDecorator def parent(self): p = self.node.getParent() if p.isEmpty() or p.getName() == "render": return self elif p.hasPythonTag("transform"): return p.getPythonTag("transform") @parent.setter def parent(self, parent): self.node.wrtReparentTo(parent.node) @SerializedPropertyDecorator def root(self): if self.parent is not self: return self.parent.root() else: return self def destroy(self): """Ultimately remove this transform. Warning: this might cause errors for other components on this game object. Use this only when removing the whole GameObject. """ self.node.removeNode() def getChildren(self): """Return children as Transforms.""" # this requires the __iter__() method return [c for c in self] def __iter__(self): """Iterate over children nodes and yield the transform instances.""" for child in self.node.getChildren(): if child.hasPythonTag("transform"): yield child.getPythonTag("transform") def __str__(self): r = "Transform for '{}'\n".format(self.name) r += "local position: {}\n".format(self.local_position) r += "local rotation: {}\n".format(self.local_euler_angles) r += "local scale: {}\n".format(self.local_scale) return r
class cMinicolumn: def __init__(self, nameOfLayer, nOfCellsPerColumn): self.cells = [] for i in range(nOfCellsPerColumn): n = cCell(self) self.cells.append(n) self.parentLayer = nameOfLayer self.transparency = 1.0 self.gfxCreated = False self.LodDistance1Stored = 100.0 self.LodDistance2Stored = 5000.0 self.bursting = False self.active = False self.oneOfCellPredictive = False self.oneOfCellCorrectlyPredicted = False self.oneOfCellFalselyPredicted = False def CreateGfx(self, loader, idx): # __node # / \ # cellsNodePath columnBox self.lod = LODNode("columnLOD") # Level of detail node for Column self.__node = NodePath( self.lod ) # NodePath(PandaNode('column'))# loader.loadModel("models/box") self.__node.setPos(0, 0, 0) self.__node.setScale(1, 1, 1) # self.__node.setTag('clickable',str(idx))#to be able to click on it self.__columnBox = loader.loadModel("models/cube") self.__columnBox.setPos( 0, 0, -0.5 + (0 if len(self.cells) == 0 else len(self.cells) * (1 + CELL_OFFSET) / 2)) self.__columnBox.setScale( 0.5, 0.5, 0.5 * (1 if len(self.cells) == 0 else len(self.cells) * (1 + CELL_OFFSET))) self.__columnBox.setName("columnBox") self.__cellsNodePath = NodePath( PandaNode("cellsNode")) # to pack all cells into one node path self.__cellsNodePath.setName("column") self.__cellsNodePath.setTag( "id", str(idx)) # to be able to retrieve index of column for mouse click self.lod.addSwitch(100.0, 0.0) self.lod.addSwitch(5000.0, 100.0) self.__cellsNodePath.reparentTo(self.__node) self.__columnBox.reparentTo(self.__node) z = 0 idx = 0 for n in self.cells: n.CreateGfx(loader, idx) idx += 1 n.getNode().setPos(0, 0, z) z += 1 + CELL_OFFSET n.getNode().reparentTo(self.__cellsNodePath) self.gfxCreated = True def LODUpdateSwitch(self, lodDistance, lodDistance2): self.lodDistance1Stored = lodDistance self.lodDistance2Stored = lodDistance2 self.lod.clearSwitches() self.lod.addSwitch(lodDistance, 0.0) self.lod.addSwitch(lodDistance2, lodDistance) def LODUpdateSwitch_long(self): self.lod.clearSwitches() self.lod.addSwitch(self.lodDistance2Stored, 0.0) self.lod.addSwitch(self.lodDistance2Stored, self.lodDistance2Stored) def LODUpdateSwitch_normal(self): self.LODUpdateSwitch(self.lodDistance1Stored, self.lodDistance2Stored) def UpdateState(self, bursting, activeColumn, oneOfCellPredictive, oneOfCellCorrectlyPredicted, oneOfCellFalselyPredicted): self.bursting = bursting self.active = activeColumn self.oneOfCellPredictive = oneOfCellPredictive self.oneOfCellCorrectlyPredicted = oneOfCellCorrectlyPredicted self.oneOfCellFalselyPredicted = oneOfCellFalselyPredicted # update column box color (for LOD in distance look) if self.oneOfCellCorrectlyPredicted: COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED.setW(self.transparency) col = COL_COLUMN_ONEOFCELLCORRECTLY_PREDICTED self.__columnBox.setColor(col) elif self.oneOfCellFalselyPredicted: COL_COLUMN_ONEOFCELLFALSELY_PREDICTED.setW(self.transparency) col = COL_COLUMN_ONEOFCELLFALSELY_PREDICTED self.__columnBox.setColor(col) elif self.oneOfCellPredictive: COL_COLUMN_ONEOFCELLPREDICTIVE.setW(self.transparency) col = COL_COLUMN_ONEOFCELLPREDICTIVE self.__columnBox.setColor(col) elif self.active: COL_COLUMN_ACTIVE.setW(self.transparency) col = COL_COLUMN_ACTIVE self.__columnBox.setColor(col) else: COL_COLUMN_INACTIVE.setW(self.transparency) col = COL_COLUMN_INACTIVE self.__columnBox.setColor(col) # for n in self.cells: # n.active = active # n.UpdateState() def getNode(self): return self.__node def updateWireframe(self, value): for cell in self.cells: cell.updateWireframe(value) if value: self.__columnBox.setRenderModeFilledWireframe(LColor(0, 0, 0, 1.0)) else: self.__columnBox.setRenderModeFilled() # -- Create proximal synapses # inputObjects - list of names of inputs(areas) # inputs - panda vis input object # synapses - list of the second points of synapses (first point is this cortical column) # NOTE: synapses are now DENSE def CreateProximalSynapses(self, inputObjects, inputs, synapses): for child in self.__cellsNodePath.getChildren(): if child.getName() == "ProximalSynapseLine": child.removeNode() printLog("Creating proximal synapses", verbosityMedium) printLog("To inputs called:" + str(inputObjects), verbosityMedium) printLog("Synapses count:" + str(len(synapses)), verbosityMedium) printLog("active:" + str(sum([i for i in synapses])), verbosityHigh) # inputs are divided into separate items in list - [input1,input2,input3] # synapses are one united array [1,0,0,1,0,1,0...] # length is the same # synapses can be connected to one input or to several inputs # if to more than one - split synapses array synapsesDiv = [] offset = 0 for inputObj in inputObjects: synapsesDiv.append(synapses[offset:offset + inputs[inputObj].count]) offset += inputs[inputObj].count for i in range(len(synapsesDiv)): # for each input object inputs[ inputObjects[i]].resetProximalFocus() # clear color highlight for y in range(len(synapsesDiv[i]) ): # go through every synapse and check activity if synapsesDiv[i][y] == 1: form = GeomVertexFormat.getV3() vdata = GeomVertexData("ProximalSynapseLine", form, Geom.UHStatic) vdata.setNumRows(1) vertex = GeomVertexWriter(vdata, "vertex") vertex.addData3f( inputs[inputObjects[i]].inputBits[y].getNode().getPos( self.__node)) vertex.addData3f(0, 0, 0) # vertex.addData3f(self.__node.getPos()) # printLog("Inputs:"+str(i)+"bits:"+str(y)) # printLog(inputs[i].inputBits[y].getNode().getPos(self.__node)) # highlight inputs[inputObjects[i]].inputBits[y].setProximalFocus( ) # highlight connected bits prim = GeomLines(Geom.UHStatic) prim.addVertices(0, 1) geom = Geom(vdata) geom.addPrimitive(prim) node = GeomNode("ProximalSynapse") node.addGeom(geom) nodePath = self.__cellsNodePath.attachNewNode(node) nodePath.setRenderModeThickness(2) # color of the line if inputs[inputObjects[i]].inputBits[y].active: nodePath.setColor(COL_PROXIMAL_SYNAPSES_ACTIVE) else: nodePath.setColor(COL_PROXIMAL_SYNAPSES_INACTIVE) def setTransparency(self, transparency): self.transparency = transparency for cell in self.cells: cell.setTransparency(transparency) self.UpdateState(self.bursting, self.active, self.oneOfCellPredictive, self.oneOfCellCorrectlyPredicted, self.oneOfCellFalselyPredicted) def DestroyProximalSynapses(self): for syn in self.__cellsNodePath.findAllMatches("ProximalSynapse"): syn.removeNode() def DestroyDistalSynapses(self): for cell in self.cells: cell.DestroyDistalSynapses() cell.resetPresynapticFocus() # also reset distal focus def getDescription(self): txt = "" txt += "Active:" + str(self.active) + "\n" txt += "One of cell is predictive:" + str( self.oneOfCellPredictive) + "\n" txt += "One of cell correctly predicted:" + str( self.oneOfCellCorrectlyPredicted) + "\n" txt += "One of cell false predicted:" + str( self.oneOfCellFalselyPredicted) + "\n" return txt
class PopupMenu(DirectObject): ''' A class to create a popup or context menu. Features : [1] it's destroyed by pressing ESCAPE, or LMB/RMB click outside of it [2] menu item's command is executed by pressing ENTER/RETURN or SPACE when it's hilighted [3] you can use arrow UP/DOWN to navigate [4] separator lines [5] menu item image [6] menu item hotkey If there are more than 1 item using the same hotkey, those items will be hilighted in cycle when the hotkey is pressed. [7] shortcut key text at the right side of menu item [8] multiple lines item text [9] menu item can have sub menus [10] it's offscreen-proof, try to put your pointer next to screen edge or corner before creating it ''' grayImages = {} # storage of grayed images, # so the same image will be converted to grayscale only once def __init__(self, items, parent=None, buttonThrower=None, onDestroy=None, font=None, baselineOffset=.0, scale=.05, itemHeight=1., leftPad=.0, separatorHeight=.5, underscoreThickness=1, BGColor=(0, 0, 0, .7), BGBorderColor=(1, .85, .4, 1), separatorColor=(1, 1, 1, 1), frameColorHover=(1, .85, .4, 1), frameColorPress=(0, 1, 0, 1), textColorReady=(1, 1, 1, 1), textColorHover=(0, 0, 0, 1), textColorPress=(0, 0, 0, 1), textColorDisabled=(.5, .5, .5, 1), minZ=None, useMouseZ=True): ''' items : a collection of menu items Item format : ( 'Item text', 'path/to/image', command ) OR ( 'Item text', 'path/to/image', command, arg1,arg2,.... ) If you don't want to use an image, pass 0. To create disabled item, pass 0 for the command : ( 'Item text', 'path/to/image', 0 ) so, you can easily switch between enabled or disabled : ( 'Item text', 'path/to/image', command if commandEnabled else 0 ) OR ( 'Item text', 'path/to/image', (0,command)[commandEnabled] ) To create submenu, pass a sequence of submenu items for the command. To create disabled submenu, pass an empty sequence for the command. To enable hotkey, insert an underscore before the character, e.g. hotkey of 'Item te_xt' is 'x' key. To add shortcut key text at the right side of the item, append it at the end of the item text, separated by "more than" sign, e.g. 'Item text>Ctrl-T'. To insert separator line, pass 0 for the whole item. parent : where to attach the menu, defaults to aspect2d buttonThrower : button thrower whose thrown events are blocked temporarily when the menu is displayed. If not given, the default button thrower is used onDestroy : user function which will be called after the menu is fully destroyed font : text font baselineOffset : text's baseline Z offset scale : text scale itemHeight : spacing between items, defaults to 1 leftPad : blank space width before text separatorHeight : separator line height, relative to itemHeight underscoreThickness : underscore line thickness BGColor, BGBorderColor, separatorColor, frameColorHover, frameColorPress, textColorReady, textColorHover, textColorPress, textColorDisabled are some of the menu components' color minZ : minimum Z position to restrain menu's bottom from going offscreen (-1..1). If it's None, it will be set a little above the screen's bottom. ''' self.parent = parent if parent else aspect2d self.onDestroy = onDestroy self.BT = buttonThrower if buttonThrower else base.buttonThrowers[ 0].node() self.menu = NodePath('menu-%s' % id(self)) self.parentMenu = None self.submenu = None self.BTprefix = self.menu.getName() + '>' self.submenuCreationTaskName = 'createSubMenu-' + self.BTprefix self.submenuRemovalTaskName = 'removeSubMenu-' + self.BTprefix self.font = font if font else TextNode.getDefaultFont() self.baselineOffset = baselineOffset if isinstance(scale, (float, int)): scale = (scale, 1.0, scale) self.scale = scale self.itemHeight = itemHeight self.leftPad = leftPad self.separatorHeight = separatorHeight self.underscoreThickness = underscoreThickness self.BGColor = BGColor self.BGBorderColor = BGBorderColor self.separatorColor = separatorColor self.frameColorHover = frameColorHover self.frameColorPress = frameColorPress self.textColorReady = textColorReady self.textColorHover = textColorHover self.textColorPress = textColorPress self.textColorDisabled = textColorDisabled self.minZ = minZ self.mpos = Point2(base.mouseWatcherNode.getMouse()) self.itemCommand = [] self.hotkeys = {} self.numItems = 0 self.sel = -1 self.selByKey = False bgPad = self.bgPad = .0125 texMargin = self.font.getTextureMargin() * self.scale[0] * .25 b = DirectButton(parent=NodePath(''), text='^|g_', text_font=self.font, scale=self.scale) fr = b.node().getFrame() b.getParent().removeNode() baselineToCenter = (fr[2] + fr[3]) * self.scale[0] LH = (fr[3] - fr[2]) * self.itemHeight * self.scale[2] imageHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .85 arrowHalfHeight = .5 * (fr[3] - fr[2]) * self.itemHeight * .5 baselineToTop = (fr[3] * self.itemHeight * self.scale[2] / LH) / (1. + self.baselineOffset) baselineToBot = LH / self.scale[2] - baselineToTop itemZcenter = (baselineToTop - baselineToBot) * .5 separatorHalfHeight = .5 * separatorHeight * LH LSseparator = LineSegs() LSseparator.setColor(.5, .5, .5, .2) arrowVtx = [ (0, itemZcenter), (-2 * arrowHalfHeight, itemZcenter + arrowHalfHeight), (-arrowHalfHeight, itemZcenter), (-2 * arrowHalfHeight, itemZcenter - arrowHalfHeight), ] tri = Triangulator() vdata = GeomVertexData('trig', GeomVertexFormat.getV3(), Geom.UHStatic) vwriter = GeomVertexWriter(vdata, 'vertex') for x, z in arrowVtx: vi = tri.addVertex(x, z) vwriter.addData3f(x, 0, z) tri.addPolygonVertex(vi) tri.triangulate() prim = GeomTriangles(Geom.UHStatic) for i in range(tri.getNumTriangles()): prim.addVertices(tri.getTriangleV0(i), tri.getTriangleV1(i), tri.getTriangleV2(i)) prim.closePrimitive() geom = Geom(vdata) geom.addPrimitive(prim) geomNode = GeomNode('arrow') geomNode.addGeom(geom) realArrow = NodePath(geomNode) z = -baselineToTop * self.scale[2] - bgPad maxWidth = .1 / self.scale[0] shortcutTextMaxWidth = 0 anyImage = False anyArrow = False anyShortcut = False arrows = [] shortcutTexts = [] loadPrcFileData('', 'text-flatten 0') for item in items: if item: t, imgPath, f = item[:3] haveSubmenu = type(f) in SEQUENCE_TYPES anyArrow |= haveSubmenu anyImage |= isinstance(imgPath, bool) or bool(imgPath) disabled = not len(f) if haveSubmenu else not callable(f) args = item[3:] underlinePos = t.find('_') t = t.replace('_', '') shortcutSepPos = t.find('>') if shortcutSepPos > -1: if haveSubmenu: print( "\nA SHORTCUT KEY POINTING TO A SUBMENU IS NON-SENSE, DON'T YOU AGREE ?" ) else: shortcutText = NodePath( OnscreenText( parent=self.menu, text=t[shortcutSepPos + 1:], font=self.font, scale=1, fg=(1, 1, 1, 1), align=TextNode.ARight, )) shortcutTextMaxWidth = max( shortcutTextMaxWidth, abs(shortcutText.getTightBounds()[0][0])) anyShortcut = True t = t[:shortcutSepPos] else: shortcutText = '' EoLcount = t.count('\n') arrowZpos = -self.font.getLineHeight() * EoLcount * .5 if disabled: b = NodePath( OnscreenText( parent=self.menu, text=t, font=self.font, scale=1, fg=textColorDisabled, align=TextNode.ALeft, )) # don't pass the scale and position to OnscreenText constructor, # to maintain correctness between the OnscreenText and DirectButton items # due to the new text generation implementation b.setScale(self.scale) b.setZ(z) maxWidth = max(maxWidth, b.getTightBounds()[1][0] / self.scale[0]) if shortcutText: shortcutText.reparentTo(b) shortcutText.setColor(Vec4(*textColorDisabled), 1) shortcutText.setZ(arrowZpos) shortcutTexts.append(shortcutText) else: b = DirectButton( parent=self.menu, text=t, text_font=self.font, scale=self.scale, pos=(0, 0, z), text_fg=textColorReady, # text color when mouse over text2_fg=textColorHover, # text color when pressed text1_fg=textColorHover if haveSubmenu else textColorPress, # framecolor when pressed frameColor=frameColorHover if haveSubmenu else frameColorPress, commandButtons=[DGG.LMB, DGG.RMB], command=(lambda: 0) if haveSubmenu else self.__runCommand, extraArgs=[] if haveSubmenu else [f, args], text_align=TextNode.ALeft, relief=DGG.FLAT, rolloverSound=0, clickSound=0, pressEffect=0) b.stateNodePath[2].setColor( *frameColorHover) # framecolor when mouse over b.stateNodePath[0].setColor(0, 0, 0, 0) # framecolor when ready bframe = Vec4(b.node().getFrame()) if EoLcount: bframe.setZ(EoLcount * 10) b['frameSize'] = bframe maxWidth = max(maxWidth, bframe[1]) if shortcutText: for snpi, col in ((0, textColorReady), (1, textColorPress), (2, textColorHover)): sct = shortcutText.copyTo(b.stateNodePath[snpi], sort=10) sct.setColor(Vec4(*col), 1) sct.setZ(arrowZpos) shortcutTexts.append(sct) shortcutText.removeNode() if isinstance(imgPath, bool): if imgPath: if disabled: fg = textColorDisabled else: fg = textColorReady tick = NodePath( OnscreenText( parent=b, text=u"\u2714", font=self.font, scale=1, fg=fg, align=TextNode.ALeft, )) tick.setX(-2 * imageHalfHeight - leftPad) elif imgPath: img = loader.loadTexture(imgPath, okMissing=True) if img is not None: if disabled: if imgPath in PopupMenu.grayImages: img = PopupMenu.grayImages[imgPath] else: pnm = PNMImage() img.store(pnm) pnm.makeGrayscale(.2, .2, .2) img = Texture() img.load(pnm) PopupMenu.grayImages[imgPath] = img img.setMinfilter(Texture.FTLinearMipmapLinear) img.setWrapU(Texture.WMClamp) img.setWrapV(Texture.WMClamp) CM = CardMaker('') CM.setFrame(-2 * imageHalfHeight - leftPad, -leftPad, itemZcenter - imageHalfHeight, itemZcenter + imageHalfHeight) imgCard = b.attachNewNode(CM.generate()) imgCard.setTexture(img) if underlinePos > -1: oneLineText = t[:underlinePos + 1] oneLineText = oneLineText[oneLineText.rfind('\n') + 1:] tn = TextNode('') tn.setFont(self.font) tn.setText(oneLineText) tnp = NodePath(tn.getInternalGeom()) underlineXend = tnp.getTightBounds()[1][0] tnp.removeNode() tn.setText(t[underlinePos]) tnp = NodePath(tn.getInternalGeom()) b3 = tnp.getTightBounds() underlineXstart = underlineXend - (b3[1] - b3[0])[0] tnp.removeNode() underlineZpos = -.7 * baselineToBot - self.font.getLineHeight( ) * t[:underlinePos].count('\n') LSunder = LineSegs() LSunder.setThickness(underscoreThickness) LSunder.moveTo(underlineXstart + texMargin, 0, underlineZpos) LSunder.drawTo(underlineXend - texMargin, 0, underlineZpos) if disabled: underline = b.attachNewNode(LSunder.create()) underline.setColor(Vec4(*textColorDisabled), 1) else: underline = b.stateNodePath[0].attachNewNode( LSunder.create()) underline.setColor(Vec4(*textColorReady), 1) underline.copyTo(b.stateNodePath[1], 10).setColor( Vec4(*textColorHover if haveSubmenu else textColorPress), 1) underline.copyTo(b.stateNodePath[2], 10).setColor(Vec4(*textColorHover), 1) hotkey = t[underlinePos].lower() if hotkey in self.hotkeys: self.hotkeys[hotkey].append(self.numItems) else: self.hotkeys[hotkey] = [self.numItems] self.accept(self.BTprefix + hotkey, self.__processHotkey, [hotkey]) self.accept(self.BTprefix + 'alt-' + hotkey, self.__processHotkey, [hotkey]) if haveSubmenu: if disabled: arrow = realArrow.instanceUnderNode(b, '') arrow.setColor(Vec4(*textColorDisabled), 1) arrow.setZ(arrowZpos) else: arrow = realArrow.instanceUnderNode( b.stateNodePath[0], 'r') arrow.setColor(Vec4(*textColorReady), 1) arrow.setZ(arrowZpos) arrPress = realArrow.instanceUnderNode( b.stateNodePath[1], 'p') arrPress.setColor(Vec4(*textColorHover), 1) arrPress.setZ(arrowZpos) arrHover = realArrow.instanceUnderNode( b.stateNodePath[2], 'h') arrHover.setColor(Vec4(*textColorHover), 1) arrHover.setZ(arrowZpos) # weird, if sort order is 0, it's obscured by the frame for a in (arrPress, arrHover): a.reparentTo(a.getParent(), sort=10) if not disabled: extraArgs = [self.numItems, f if haveSubmenu else 0] self.accept(DGG.ENTER + b.guiId, self.__hoverOnItem, extraArgs) self.accept(DGG.EXIT + b.guiId, self.__offItem) #~ self.itemCommand.append((None,0) if haveSubmenu else (f,args)) self.itemCommand.append((f, args)) if self.numItems == 0: self.firstButtonIdx = int(b.guiId[2:]) self.numItems += 1 z -= LH + self.font.getLineHeight() * self.scale[2] * EoLcount else: # SEPARATOR LINE z += LH - separatorHalfHeight - baselineToBot * self.scale[2] LSseparator.moveTo(0, 0, z) LSseparator.drawTo(self.scale[0] * .5, 0, z) LSseparator.drawTo(self.scale[0], 0, z) z -= separatorHalfHeight + baselineToTop * self.scale[2] maxWidth += 7 * arrowHalfHeight * ( anyArrow or anyShortcut) + .2 + shortcutTextMaxWidth arrowXpos = maxWidth - arrowHalfHeight realArrow.setX(arrowXpos) if anyImage: leftPad += 2 * imageHalfHeight + leftPad for sct in shortcutTexts: sct.setX(maxWidth - 2 * (arrowHalfHeight * anyArrow + .2)) for c in asList(self.menu.findAllMatches('**/DirectButton*')): numLines = c.node().getFrame()[2] c.node().setFrame( Vec4( -leftPad, maxWidth, -baselineToBot - (numLines * .1 * self.itemHeight if numLines >= 10 else 0), baselineToTop)) loadPrcFileData('', 'text-flatten 1') try: minZ = self.menu.getChild(0).getRelativePoint( b, Point3(0, 0, b.node().getFrame()[2]))[2] except: minZ = self.menu.getChild(0).getRelativePoint( self.menu, Point3( 0, 0, b.getTightBounds()[0][2]))[2] - baselineToBot * .5 try: top = self.menu.getChild(0).node().getFrame()[3] except: top = self.menu.getChild(0).getZ() + baselineToTop l, r, b, t = -leftPad - bgPad / self.scale[ 0], maxWidth + bgPad / self.scale[0], minZ - bgPad / self.scale[ 2], top + bgPad / self.scale[2] menuBG = DirectFrame(parent=self.menu.getChild(0), frameSize=(l, r, b, t), frameColor=BGColor, state=DGG.NORMAL, suppressMouse=1) menuBorder = self.menu.getChild(0).attachNewNode('border') borderVtx = ( (l, 0, b), (l, 0, .5 * (b + t)), (l, 0, t), (.5 * (l + r), 0, t), (r, 0, t), (r, 0, .5 * (b + t)), (r, 0, b), (.5 * (l + r), 0, b), (l, 0, b), ) LSborderBG = LineSegs() LSborderBG.setThickness(4) LSborderBG.setColor(0, 0, 0, .7) LSborderBG.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborderBG.drawTo(*v) # fills the gap at corners for v in range(0, 7, 2): LSborderBG.moveTo(*(borderVtx[v])) menuBorder.attachNewNode(LSborderBG.create()) LSborder = LineSegs() LSborder.setThickness(2) LSborder.setColor(*BGBorderColor) LSborder.moveTo(*(borderVtx[0])) for v in borderVtx[1:]: LSborder.drawTo(*v) menuBorder.attachNewNode(LSborder.create()) for v in range(1, 8, 2): LSborderBG.setVertexColor(v, Vec4(0, 0, 0, .1)) LSborder.setVertexColor(v, Vec4(.3, .3, .3, .5)) menuBorderB3 = menuBorder.getTightBounds() menuBorderDims = menuBorderB3[1] - menuBorderB3[0] menuBG.wrtReparentTo(self.menu, sort=-1) self.menu.reparentTo(self.parent) x = -menuBorderB3[0][0] * self.scale[0] for c in asList(self.menu.getChildren()): c.setX(x) self.maxWidth = maxWidth = menuBorderDims[0] self.height = menuBorderDims[2] maxWidthR2D = maxWidth * self.menu.getChild(0).getSx(render2d) separatorLines = self.menu.attachNewNode(LSseparator.create(), 10) separatorLines.setSx(maxWidth) for v in range(1, LSseparator.getNumVertices(), 3): LSseparator.setVertexColor(v, Vec4(*separatorColor)) x = clamp(-.98, .98 - maxWidthR2D, self.mpos[0] - maxWidthR2D * .5) minZ = (-.98 if self.minZ is None else self.minZ) z = clamp( minZ + menuBorderDims[2] * self.scale[2] * self.parent.getSz(render2d), .98, self.mpos[1] if useMouseZ else -1000) self.menu.setPos(render2d, x, 0, z) self.menu.setTransparency(1) self.origBTprefix = self.BT.getPrefix() self.BT.setPrefix(self.BTprefix) self.accept(self.BTprefix + 'escape', self.destroy) for e in ('mouse1', 'mouse3'): self.accept(self.BTprefix + e, self.destroy, [True]) self.accept(self.BTprefix + 'arrow_down', self.__nextItem) self.accept(self.BTprefix + 'arrow_down-repeat', self.__nextItem) self.accept(self.BTprefix + 'arrow_up', self.__prevItem) self.accept(self.BTprefix + 'arrow_up-repeat', self.__prevItem) self.accept(self.BTprefix + 'enter', self.__runSelItemCommand) self.accept(self.BTprefix + 'space', self.__runSelItemCommand) def __offItem(self, crap): self.sel = -1 self.__cancelSubmenuCreation() def __hoverOnItem(self, idx, menu, crap): self.sel = idx self.__cancelSubmenuCreation() if self.BT.getPrefix()==self.BTprefix or \ (self.submenu and self.submenuIdx==idx): self.__cancelSubmenuRemoval() if menu: if not (self.submenu and self.submenuIdx == idx): #~ if self.selByKey: #~ self.selByKey=False #~ self.__createSubmenu(idx,menu) #~ else: taskMgr.doMethodLater(.3, self.__createSubmenu, self.submenuCreationTaskName, extraArgs=[idx, menu]) else: taskMgr.doMethodLater(.5, self.__removeSubmenu, self.submenuRemovalTaskName, extraArgs=[]) def __cancelSubmenuCreation(self): taskMgr.removeTasksMatching('createSubMenu-*') def __createSubmenu(self, idx, menu): self.__cancelSubmenuCreation() self.__removeSubmenu() self.submenu = PopupMenu(items=menu, parent=self.parent, buttonThrower=self.BT, font=self.font, baselineOffset=self.baselineOffset, scale=self.scale, itemHeight=self.itemHeight, leftPad=self.leftPad, separatorHeight=self.separatorHeight, underscoreThickness=self.underscoreThickness, BGColor=self.BGColor, BGBorderColor=self.BGBorderColor, separatorColor=self.separatorColor, frameColorHover=self.frameColorHover, frameColorPress=self.frameColorPress, textColorReady=self.textColorReady, textColorHover=self.textColorHover, textColorPress=self.textColorPress, textColorDisabled=self.textColorDisabled, minZ=self.minZ, useMouseZ=False) self.submenuIdx = idx self.submenu.parentMenu = self if self.menu.getBinName(): self.submenu.menu.setBin(self.menu.getBinName(), self.menu.getBinDrawOrder() + 1) sb3 = self.submenu.menu.getTightBounds() sb = sb3[1] - sb3[0] b3 = self.menu.getTightBounds() x = b3[1][0] if render2d.getRelativePoint(self.parent, Point3(x + sb[0], 0, 0))[0] > .98: x = b3[0][0] - sb[0] if render2d.getRelativePoint(self.parent, Point3(x, 0, 0))[0] < -.98: x = self.parent.getRelativePoint(render2d, Point3(-.98, 0, 0))[0] item = self.menu.find('**/*-pg%s' % (self.firstButtonIdx + idx)) z = self.parent.getRelativePoint( item, Point3(0, 0, item.node().getFrame()[3]))[2] + self.bgPad self.submenu.menu.setPos(x, 0, max(z, self.submenu.menu.getZ())) # self.submenu.menu.setPos(x,0,z) def __nextItem(self): if self.numItems: self.sel = clamp(0, self.numItems - 1, self.sel + 1) self.__putPointerAtItem() self.selByKey = True def __prevItem(self): if self.numItems: self.sel = clamp(0, self.numItems - 1, (self.sel - 1) if self.sel > -1 else self.numItems - 1) self.__putPointerAtItem() self.selByKey = True def __putPointerAtItem(self): item = self.menu.find('**/*-pg%s' % (self.firstButtonIdx + self.sel)) fr = item.node().getFrame() c = Point3(.5 * (fr[0] + fr[1]), 0, .5 * (fr[2] + fr[3])) cR2D = render2d.getRelativePoint(item, c) x, y = int(base.win.getXSize() * .5 * (cR2D[0] + 1)), int( base.win.getYSize() * .5 * (-cR2D[2] + 1)) if '__origmovePointer' in base.win.DtoolClassDict: base.win.DtoolClassDict['__origmovePointer'](base.win, 0, x, y) else: base.win.movePointer(0, x, y) def __processHotkey(self, hotkey): itemsIdx = self.hotkeys[hotkey] if len(itemsIdx) == 1 and type( self.itemCommand[itemsIdx[0]][0]) not in SEQUENCE_TYPES: self.__runCommand(*self.itemCommand[itemsIdx[0]]) else: if self.sel in itemsIdx: idx = itemsIdx.index(self.sel) + 1 idx %= len(itemsIdx) self.sel = itemsIdx[idx] else: self.sel = itemsIdx[0] self.selByKey = True # if it's already there, putting the pointer doesn't trigger the 'enter' # event, so just bypass it if not (self.submenu and self.submenuIdx==self.sel) and\ type(self.itemCommand[itemsIdx[0]][0]) in SEQUENCE_TYPES: self.__createSubmenu(self.sel, self.itemCommand[itemsIdx[0]][0]) self.__putPointerAtItem() def __doRunCommand(self, f, args): self.destroy(delParents=True) f(*args) def __runCommand(self, f, args): if callable(f): # must be done at next frame, so shortcut key event won't bleed to the scene taskMgr.doMethodLater(.01, self.__doRunCommand, 'run menu command', extraArgs=[f, args]) def __runSelItemCommand(self): if self.sel == -1: return self.__runCommand(*self.itemCommand[self.sel]) def __cancelSubmenuRemoval(self): taskMgr.removeTasksMatching('removeSubMenu-*') def __removeSubmenu(self): self.__cancelSubmenuRemoval() if self.submenu: self.submenu.destroy() def destroy(self, delParents=False): self.__cancelSubmenuCreation() self.__removeSubmenu() self.subMenu = None self.ignoreAll() self.menu.removeNode() # if self.origBTprefix.find('menu-')==-1: # taskMgr.step() self.BT.setPrefix(self.origBTprefix) messenger.send(self.BTprefix + 'destroyed') if delParents and self.parentMenu: parent = self.parentMenu while parent.parentMenu: parent = parent.parentMenu parent.destroy() if self.parentMenu: self.parentMenu.submenuIdx = None self.parentMenu = None if callable(self.onDestroy): self.onDestroy()
class BaseObject(BaseRunnable): """ BaseObject is something interacting with game engine. If something is expected to have a body in the world or have appearance in the world, it must be a subclass of BaseObject. It is created with name/config/randomEngine and can make decision in the world. Besides the random engine can help sample some special configs for it ,Properties and parameters in PARAMETER_SPACE of the object are fixed after calling __init__(). """ def __init__(self, name=None, random_seed=None, config=None, escape_random_seed_assertion=False): """ Config is a static conception, which specified the parameters of one element. There parameters doesn't change, such as length of straight road, max speed of one vehicle, etc. """ super(BaseObject, self).__init__(name, random_seed, config) if not escape_random_seed_assertion: assert random_seed is not None, "Please assign a random seed for {} class.".format( self.class_name) # Following properties are available when this object needs visualization and physics property self._body = None # each element has its node_path to render, physics node are child nodes of it self.origin = NodePath(self.name) # Temporally store bullet nodes that have to place in bullet world (not NodePath) self.dynamic_nodes = PhysicsNodeList() # Nodes in this tuple didn't interact with other nodes! they only used to do rayTest or sweepTest self.static_nodes = PhysicsNodeList() # render or not self.render = False if AssetLoader.loader is None else True if self.render: self.loader = AssetLoader.get_loader() if not hasattr(self.loader, "loader"): # It is closed before! self.loader.__init__() def add_body(self, physics_body): if self._body is None: # add it to physics world, in which this object will interact with other object (like collision) if not isinstance(physics_body, BulletBodyNode): raise ValueError("The physics body is not BulletBodyNode type") self._body = physics_body new_origin = NodePath(self._body) self.origin.getChildren().reparentTo(new_origin) self.origin = new_origin self.dynamic_nodes.append(physics_body) else: raise AttributeError("You can not set the object body for twice") @property def body(self): if self._body.hasPythonTag(self._body.getName()): return self._body.getPythonTag(self._body.getName()) else: return self._body def attach_to_world(self, parent_node_path: NodePath, physics_world: PhysicsWorld): """ Load to world from memory """ if self.render: # double check :-) assert isinstance( self.origin, NodePath), "No render model on node_path in this Element" self.origin.reparentTo(parent_node_path) self.dynamic_nodes.attach_to_physics_world(physics_world.dynamic_world) self.static_nodes.attach_to_physics_world(physics_world.static_world) def detach_from_world(self, physics_world: PhysicsWorld): """ It is not fully remove, it will be left in memory. if this element is useless in the future, call Func delete() """ if self.origin is not None: self.origin.detachNode() self.dynamic_nodes.detach_from_physics_world( physics_world.dynamic_world) self.static_nodes.detach_from_physics_world(physics_world.static_world) def destroy(self): """ Fully delete this element and release the memory """ from pgdrive.engine.engine_utils import get_engine engine = get_engine() self.detach_from_world(engine.physics_world) if self._body is not None and hasattr(self.body, "object"): self.body.generated_object = None if self.origin is not None: self.origin.removeNode() self.dynamic_nodes.clear() self.static_nodes.clear() self._config.clear()
class FractalTree(NodePath): """ Base class for fractal trees """ def __init__(self, barkTexture, leafModel, lengthList, numCopiesList, radiusList): """ make tree from params """ NodePath.__init__(self, "Tree Holder") self.numPrimitives = 0 self.leafModel = leafModel self.barkTexture = barkTexture self.bodies = NodePath("Bodies") self.leaves = NodePath("Leaves") self.coll = self.attachNewNode(CollisionNode("Collision")) self.bodydata = GeomVertexData("body vertices", GeomVertexFormat.getV3n3t2(), Geom.UHStatic) self.drawFlags = set() self.numCopiesList = list(numCopiesList) self.radiusList = list(radiusList) self.lengthList = list(lengthList) self.iterations = 1 self.makeEnds() self.makeFromStack(True) #self.coll.show() self.bodies.setTexture(barkTexture) self.coll.reparentTo(self) self.bodies.reparentTo(self) self.leaves.reparentTo(self) def getStatic(self): """ this makes a flattened version of the tree for faster rendering... """ np = NodePath(self.node().copySubgraph()) np.flattenStrong() return np def makeEnds(self, pos=Vec3(0, 0, 0), quat=None): """ add initial stem-end to the stack of ends """ if quat is None: quat = Quat() self.ends = [(pos, quat, 0)] def makeFromStack(self, makeColl=False): """ update tree geometry using existing ends """ stack = self.ends to = self.iterations lengthList = self.lengthList numCopiesList = self.numCopiesList radiusList = self.radiusList ends = [] while stack: pos, quat, depth = stack.pop() length = lengthList[depth] if depth != to and depth + 1 < len(lengthList): self.drawBody(pos, quat, radiusList[depth]) #move foward along the right axis newPos = pos + quat.xform(length) if makeColl: self.makeColl(pos, newPos, radiusList[depth]) numCopies = numCopiesList[depth] if numCopies: for i in xrange(numCopies): stack.append( (newPos, _angleRandomAxis(quat, 2 * math.pi * i / numCopies), depth + 1)) #stack.append((newPos, _randomAxis(vecList,3), #depth + 1)) else: #just make another branch connected to this one with a #small variation in direction stack.append((newPos, _randomBend(quat, 20), depth + 1)) else: ends.append((pos, quat, depth)) self.drawBody(pos, quat, radiusList[depth], False) self.drawLeaf(pos, quat) self.ends = ends def makeColl(self, pos, newPos, radius): """ make a collision tube for the given stem-parameters """ tube = CollisionTube(Point3(pos), Point3(newPos), radius) self.coll.node().addSolid(tube) def drawBody(self, pos, quat, radius=1, keepDrawing=True, numVertices=16): """ this draws the body of the tree. This draws a ring of vertices and connects the rings with triangles to from the body. the keepDrawing parameter tells the function whether or not we're at an end if the vertices before were an end, don't draw branches to it """ vdata = self.bodydata circleGeom = Geom(vdata) vertWriter = GeomVertexWriter(vdata, "vertex") normalWriter = GeomVertexWriter(vdata, "normal") texReWriter = GeomVertexRewriter(vdata, "texcoord") startRow = vdata.getNumRows() vertWriter.setRow(startRow) normalWriter.setRow(startRow) sCoord = 0 if (startRow != 0): texReWriter.setRow(startRow - numVertices) sCoord = texReWriter.getData2f().getX() + 1 draw = (startRow - numVertices) in self.drawFlags if not draw: sCoord -= 1 drawIndex = startRow texReWriter.setRow(startRow) angleSlice = 2 * math.pi / numVertices currAngle = 0 perp1 = quat.getRight() perp2 = quat.getForward() #vertex information is written here for i in xrange(numVertices + 1): #doubles the last vertex to fix UV seam adjCircle = pos + (perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle)) * radius normal = perp1 * math.cos(currAngle) + perp2 * math.sin(currAngle) normalWriter.addData3f(normal) vertWriter.addData3f(adjCircle) texReWriter.addData2f(1.0 * i / numVertices, sCoord) if keepDrawing: self.drawFlags.add(drawIndex) drawIndex += 1 currAngle += angleSlice draw = (startRow - numVertices) in self.drawFlags #we cant draw quads directly so we use Tristrips if (startRow != 0) and draw: lines = GeomTristrips(Geom.UHStatic) for i in xrange(numVertices + 1): lines.addVertex(i + startRow) lines.addVertex(i + startRow - numVertices - 1) lines.addVertex(startRow) lines.addVertex(startRow - numVertices) lines.closePrimitive() #lines.decompose() circleGeom.addPrimitive(lines) circleGeomNode = GeomNode("Debug") circleGeomNode.addGeom(circleGeom) self.numPrimitives += numVertices * 2 self.bodies.attachNewNode(circleGeomNode) def drawLeaf(self, pos=Vec3(0, 0, 0), quat=None, scale=0.125): """ this draws leafs when we reach an end """ #use the vectors that describe the direction the branch grows to make #the right rotation matrix newCs = Mat4() quat.extractToMatrix(newCs) axisAdj = Mat4.scaleMat(scale) * newCs * Mat4.translateMat(pos) leafModel = NodePath("leaf") self.leafModel.instanceTo(leafModel) leafModel.reparentTo(self.leaves) leafModel.setTransform(TransformState.makeMat(axisAdj)) def grow(self, num=1, removeLeaves=True, leavesScale=1, scale=1.125): """ Grows the tree num steps """ self.iterations += num while num > 0: self.setScale(self, scale) self.leafModel.setScale(self.leafModel, leavesScale / scale) if removeLeaves: for c in self.leaves.getChildren(): c.removeNode() self.makeFromStack() self.bodies.setTexture(self.barkTexture) num -= 1
class GUIController(DirectObject.DirectObject): ''' Manages subwindows. Use this to make common windows to prevent import hell ''' def __init__(self, script, language = "english"): """ Script be the language database """ self.script = script #messenger.send('makePickable', [base.a2dBottomLeft]) self.window = None self.language = language self.accept("onGetMaps", self.mapSelection) self.accept("finishedTerrainGen", self.regionGUI) self.accept("found_city_name", self.foundCityName) self.accept("newCityResponse", self.newCityResponse) self.accept("updateCityLabels", self.cityLabels) self.accept("showRegionCityWindow", self.regionCityWindow) self.accept("showRegionGUI", self.regionGUI) self.accept("disconnected", self.disconnected) self.accept("enterCityView", self.cityGUI) self.cityLabels = NodePath("cityLabels") self.cityLabels.reparentTo(render) self.text = OnscreenText() self.debug() def getText(self, text): return self.script.getText(text, self.language) def makeMainMenu(self): """ Creates main menu """ if self.window: self.window.destroy() self.window = None self.mainMenu = DirectWindow(title = self.getText("TXT_UI_MAINMENUTITLE")) login = DirectButton(text= self.getText('TXT_UI_LOGINMP'), command=self.loginMP) closeButton = DirectButton( text='Quit', command=lambda : messenger.send('exit')) self.mainMenu.addVertical([login, closeButton]) self.mainMenu.reset() self.mainMenu.center() message = MessageWindow(text="Your city has been founded! Awesome!") def newGame(self): self.mainMenu.destroy() messenger.send('newSPGame') def loginMP(self): self.mainMenu.destroy() self.window = DirectWindow(title = self.getText("TXT_UI_LOGINTITLE")) hostEntry = DirectEntry(initialText="croxis.dyndns.org", suppressKeys = True) userNameEntry = DirectEntry(initialText = self.getText('TXT_MAYOR_NAME'),suppressKeys = True) userPasswordEntry = DirectEntry(initialText="Password", obscured=True,suppressKeys = True) okButton = DirectButton(text = self.getText('TXT_UI_OK'), command = self.login) closeButton = DirectButton(text='Back', command=self.makeMainMenu) self.window.addVertical([hostEntry,userNameEntry,userPasswordEntry]) self.window.addHorizontal([okButton, closeButton]) def login(self): """ Collects login information from gui and fires message to login """ info = self.window.getEntries() self.window.destroy() # Password will never make it out of here unscrambled! import hashlib password = info.pop(-1) cypher = hashlib.md5(password).hexdigest() info.append(cypher) messenger.send("connect", info) def mapSelection(self, maps): """ Generate a window with list and thumbnails of region maps availiable. Provide two options, load map or, if there is no local version, save local version """ self.mapDialog = DirectWindow(title = "Select Map") mapList = [] m = [""] import base64 for mapName in maps: heightmap = maps[mapName] image = PNMImage() image.read(StringStream(heightmap)) thumbnail = PNMImage(64, 64) thumbnail.gaussianFilterFrom(1, image) heightTexture = Texture() heightTexture.load(image) label = DirectRadioButton(text=mapName, image=heightTexture, variable=m, value=[mapName]) mapList.append(label) for button in mapList: button.setOthers(mapList) self.mapDialog.addScrolledList(mapList) okButton = DirectButton(text = self.getText('TXT_UI_OK'), command = self.selectMap, extraArgs=m) self.mapDialog.addVertical([okButton]) def selectMap(self, map_name): '''Sends selected map to server for loading''' self.mapDialog.destroy() container = proto.Container() container.mapRequest = map_name messenger.send("sendData", [container]) def cityGUI(self, ident, city, position, tiles): self.regionWindow.destroy() self.cityInfoWindow.destroy() children = self.cityLabels.getChildren() for child in children: child.removeNode() #text = OnscreenText(text = "Welcome to " + city['name'], pos=(0, 0.75), scale=0.07) self.cityWindow = DirectWindow(title = city['name']) returnRegion = DirectButton(text = "Exit City", command=self.returnRegion) exitGame = DirectButton(text='Exit Game', command = self.exitGame) self.cityWindow.add([returnRegion, exitGame]) def returnRegion(self): self.cityWindow.destroy() container = proto.Container() container.requestExitCity = True messenger.send("sendData", [container]) messenger.send('regenerateRegion') def exitGame(self): messenger.send('exit') def regionGUI(self, size=[0,0]): '''Generates GUI for region view interface''' self.text.destroy() #self.loginDialog = pw.StandardWindow(title = self.script.getText("TXT_UI_REGIONTITLE"), center = True) self.regionWindow = DirectWindow(title = "Region_Name") self.v = [0] #buttons = [ # DirectRadioButton(text = "Normal View", variable=self.v, value=[0], command=self.sendRegionMessage), # DirectRadioButton(text = "Ownership View", variable=self.v, value=[1], command=self.sendRegionMessage)] buttons = [DirectButton(text = "Normal View", command=self.n), DirectButton(text = "Ownership View", command=self.o)] #for button in buttons: # button.setOthers(buttons) newCityButton = closeButton = DirectButton(text='Incorperate New City', command=self.sendRegionMessage, extraArgs=[True]) self.regionWindow.addVertical(buttons + [newCityButton]) def n(self): messenger.send("regionView_normal") def o(self): messenger.send("regionView_owners") def sendRegionMessage(self, status=None): '''Send messages for region view''' if self.v == [0]: messenger.send("regionView_normal") elif self.v == [1]: messenger.send("regionView_owners") if status: self.text = OnscreenText(text = "Left click to found city.\nEsc to cancel", pos=(0, 0.75), scale=0.07) self.regionWindow.destroy() messenger.send("regionView_owners") messenger.send("regionView_foundNew") def foundCityName(self, position): self.text.destroy() self.name_city_window = DirectWindow(title = "name_city") cityNameEntry = DirectEntry(initialText = "city_name", suppressKeys = True) okButton = DirectButton(text = self.getText('TXT_UI_OK'), command = self.foundCity, extraArgs=[position]) self.name_city_window.addVertical([cityNameEntry, okButton]) def foundCity(self, position): '''Sends message to server that we want to found a new city at this location''' container = proto.Container() info = self.name_city_window.getEntries() self.name_city_window.destroy() container.newCityRequest.name = info[0] container.newCityRequest.positionx = position[0] container.newCityRequest.positiony = position[1] messenger.send("sendData", [container]) def newCityResponse(self, info): if not info.type: message = MessageWindow(text="City can not be founded. "+ info.message, title="Oh noes!") messenger.send("regionView_foundNew") else: message = MessageWindow(text="Your city has been founded! Awesome!") messenger.send("regionView_normal") def makeChatWindow(self): pass def cityLabels(self, citylabels, terrain): children = self.cityLabels.getChildren() for child in children: child.removeNode() for ident, city in citylabels.items(): text = city['name'] + "\n" + city["mayor"] + "\n" + "Population: " + str(city['population']) label = TextNode(str(ident) + " label") label.setText(text) label.setTextColor(1, 1, 1, 1) label.setShadowColor(0, 0, 0, 1) label.setCardDecal(True) textNodePath = self.cityLabels.attachNewNode(label) textNodePath.setPos(city['position'][0], city["position"][1], city['position'][2]) textNodePath.setLightOff() textNodePath.setBillboardPointEye() def debug(self): '''Generates on screen text for debug functions.''' text = "f: toggles wireframe\nt: toggles texture\nh: switch water" OnscreenText(text = text, pos = (-1.0, 0.9), scale = 0.07) def regionCityWindow(self, ident, city): '''Generates window displaying city stats and options.''' #print "Access username and level", access.username, access.level #TODO: Once the city view is created we need to inform the client if they even have viewing rights self.cityInfoWindow = DirectWindow(title = city['name']) buttons =[] enterButton = DirectButton(text = self.script.getText('TXT_ENTER_CITY', self.language), command = self.enterCity, extraArgs=[ident]) buttons.append(enterButton) if access.level is 4 or access.username == city['mayor']: unfoundButton = DirectButton(text = self.script.getText('TXT_UNFOUND_CITY', self.language), command = self.confirmUnfoundCity, extraArgs=[ident, self.cityInfoWindow]) buttons.append(unfoundButton) #deleteButton = DirectButton(text = self.script.getText('TXT_DELETE_CITY', self.language), command = self.confirmDeleteCity, extraArgs=[ident]) #buttons.append(deleteButton) self.cityInfoWindow.addHorizontal(buttons) closeButton = DirectButton(text = self.script.getText('TXT_BUTTON_CLOSE', self.language), command = self.closeWindow, extraArgs=[self.cityInfoWindow]) self.cityInfoWindow.addVertical([closeButton]) def enterCity(self, ident): container = proto.Container() container.requestEnterCity = ident messenger.send("sendData", [container]) #def exitCity(self): def confirmUnfoundCity(self, ident, cityWindow): window = DirectWindow(title = self.getText("TXT_TITLE_COFIRM_UNFOUND")) okButton = DirectButton(text = self.getText("TXT_BUTTON_CONFIRM_UNFOUND"), command = self.unfoundCity, extraArgs = [ident, window, cityWindow]) closeButton = DirectButton(text = self.getText('TXT_BUTTON_CLOSE'), command = self.closeWindow, extraArgs=[window]) window.addHorizontal([okButton, closeButton]) def unfoundCity(self, ident, window, cityWindow): self.closeWindow(window) self.closeWindow(cityWindow) container = proto.Container() container.requestUnfoundCity = ident messenger.send("sendData", [container]) def confirmDeleteCity(self, ident): print "Request to delete city", ident def closeWindow(self, window): window.destroy() def disconnected(self, reason): render.removeChildren() base.aspect2d.removeChildren() self.makeMainMenu() message = MessageWindow(title="You have been disconnected :(", text="Reason: %s" %reason)
class CollisionChecker(object): """ A fast collision checker that allows maximum 32 collision pairs author: weiwei date: 20201214osaka """ def __init__(self, name="auto"): self.ctrav = CollisionTraverser() self.chan = CollisionHandlerQueue() self.np = NodePath(name) self.bitmask_list = [BitMask32(2**n) for n in range(31)] self._bitmask_ext = BitMask32( 2**31) # 31 is prepared for cd with external non-active objects self.all_cdelements = [ ] # a list of cdlnks or cdobjs for quick accessing the cd elements (cdlnks/cdobjs) def add_cdlnks(self, jlcobj, lnk_idlist): """ The collision node of the given links will be attached to self.np, but their collision bitmask will be cleared When the a robot_s is treated as an obstacle by another robot_s, the IntoCollideMask of its all_cdelements will be set to BitMask32(2**31), so that the other robot_s can compare its active_cdelements with the all_cdelements. :param jlcobj: :param lnk_idlist: :return: author: weiwei date: 20201216toyonaka """ for id in lnk_idlist: if jlcobj.lnks[id]['cdprimit_childid'] == -1: # first time add cdnp = jlcobj.lnks[id]['collision_model'].copy_cdnp_to( self.np, clearmask=True) self.ctrav.addCollider(cdnp, self.chan) self.all_cdelements.append(jlcobj.lnks[id]) jlcobj.lnks[id]['cdprimit_childid'] = len( self.all_cdelements) - 1 else: raise ValueError("The link is already added!") def set_active_cdlnks(self, activelist): """ The specified collision links will be used for collision detection with external obstacles :param activelist: essentially a from list like [jlchain.lnk0, jlchain.lnk1...] the correspondent tolist will be set online in cd functions TODO use all elements in self.all_cdnlks if None :return: author: weiwei date: 20201216toyonaka """ for cdlnk in activelist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the add_cdlnks function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) cdnp.node().setFromCollideMask(self._bitmask_ext) def set_cdpair(self, fromlist, intolist): """ The given collision pair will be used for self collision detection :param fromlist: [[bool, cdprimit_cache], ...] :param intolist: [[bool, cdprimit_cache], ...] :return: author: weiwei date: 20201215 """ if len(self.bitmask_list) == 0: raise ValueError("Too many collision pairs! Maximum: 29") allocated_bitmask = self.bitmask_list.pop() for cdlnk in fromlist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the addjlcobj function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_from_cdmask = cdnp.node().getFromCollideMask() new_from_cdmask = current_from_cdmask | allocated_bitmask cdnp.node().setFromCollideMask(new_from_cdmask) for cdlnk in intolist: if cdlnk['cdprimit_childid'] == -1: raise ValueError( "The link needs to be added to collider using the addjlcobj function first!" ) cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask | allocated_bitmask cdnp.node().setIntoCollideMask(new_into_cdmask) def add_cdobj(self, objcm, rel_pos, rel_rotmat, into_list): """ :return: cdobj_info, a dictionary that mimics a joint link; Besides that, there is an additional 'into_list' key to hold into_list to easily toggle off the bitmasks. """ cdobj_info = {} cdobj_info['collision_model'] = objcm # for reversed lookup cdobj_info['gl_pos'] = objcm.get_pos() cdobj_info['gl_rotmat'] = objcm.get_rotmat() cdobj_info['rel_pos'] = rel_pos cdobj_info['rel_rotmat'] = rel_rotmat cdobj_info['into_list'] = into_list cdnp = objcm.copy_cdnp_to(self.np, clearmask=True) cdnp.node().setFromCollideMask(self._bitmask_ext) # set active self.ctrav.addCollider(cdnp, self.chan) self.all_cdelements.append(cdobj_info) cdobj_info['cdprimit_childid'] = len(self.all_cdelements) - 1 self.set_cdpair([cdobj_info], into_list) return cdobj_info def delete_cdobj(self, cdobj_info): """ :param cdobj_info: an lnk-like object generated by self.add_objinhnd :param objcm: :return: """ self.all_cdelements.remove(cdobj_info) cdnp_to_delete = self.np.getChild(cdobj_info['cdprimit_childid']) self.ctrav.removeCollider(cdnp_to_delete) this_cdmask = cdnp_to_delete.node().getFromCollideMask() for cdlnk in cdobj_info['into_list']: cdnp = self.np.getChild(cdlnk['cdprimit_childid']) current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask & ~this_cdmask cdnp.node().setIntoCollideMask(new_into_cdmask) cdnp_to_delete.detachNode() self.bitmask_list.append(this_cdmask) def is_collided(self, obstacle_list=[], otherrobot_list=[], toggle_contact_points=False): """ :param obstacle_list: staticgeometricmodel :param otherrobot_list: :return: """ for cdelement in self.all_cdelements: pos = cdelement['gl_pos'] rotmat = cdelement['gl_rotmat'] cdnp = self.np.getChild(cdelement['cdprimit_childid']) cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat)) # print(da.npv3mat3_to_pdmat4(pos, rotmat)) # print("From", cdnp.node().getFromCollideMask()) # print("Into", cdnp.node().getIntoCollideMask()) # print("xxxx colliders xxxx") # for collider in self.ctrav.getColliders(): # print(collider.getMat()) # print("From", collider.node().getFromCollideMask()) # print("Into", collider.node().getIntoCollideMask()) # attach obstacles obstacle_parent_list = [] for obstacle in obstacle_list: obstacle_parent_list.append(obstacle.objpdnp.getParent()) obstacle.objpdnp.reparentTo(self.np) # attach other robots for robot in otherrobot_list: for cdnp in robot.cc.np.getChildren(): current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask | self._bitmask_ext cdnp.node().setIntoCollideMask(new_into_cdmask) robot.cc.np.reparentTo(self.np) # collision check self.ctrav.traverse(self.np) # clear obstacles for i, obstacle in enumerate(obstacle_list): obstacle.objpdnp.reparentTo(obstacle_parent_list[i]) # clear other robots for robot in otherrobot_list: for cdnp in robot.cc.np.getChildren(): current_into_cdmask = cdnp.node().getIntoCollideMask() new_into_cdmask = current_into_cdmask & ~self._bitmask_ext cdnp.node().setIntoCollideMask(new_into_cdmask) robot.cc.np.detachNode() if self.chan.getNumEntries() > 0: collision_result = True else: collision_result = False if toggle_contact_points: contact_points = [ da.pdv3_to_npv3(cd_entry.getSurfacePoint(base.render)) for cd_entry in self.chan.getEntries() ] return collision_result, contact_points else: return collision_result def show_cdprimit(self): """ Copy the current nodepath to base.render to show collision states TODO: maintain a list to allow unshow :return: author: weiwei date: 20220404 """ # print("call show_cdprimit") snp_cpy = self.np.copyTo(base.render) for cdelement in self.all_cdelements: pos = cdelement['gl_pos'] rotmat = cdelement['gl_rotmat'] cdnp = snp_cpy.getChild(cdelement['cdprimit_childid']) cdnp.setPosQuat(da.npv3_to_pdv3(pos), da.npmat3_to_pdquat(rotmat)) cdnp.show() def disable(self): """ clear pairs and nodepath :return: """ for cdelement in self.all_cdelements: cdelement['cdprimit_childid'] = -1 self.all_cdelements = [] for child in self.np.getChildren(): child.removeNode() self.bitmask_list = list(range(31))
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 destroy(self): """Destroy all assets and removes the root node. This makes an instance of AssetManager unusable!""" for a in self.assets: a.destroy() self.assets = [] self.root = None def __repr__(self): r = "AssetManager for root {}:".format(self.root) for a in self.assets: r += "\n\t{} {}".format(type(a).__name__, a.name) return r # Test # Needs assets.empty.Empty if __name__ == "__main__": from panda3d.core import NodePath root = NodePath("root") am = AssetManager(root) id0 = am.add("Empty", "empty test asset 1") id1 = am.add("Empty", "empty test asset 2") assert id0 == 0 and id1 == 1 assert am.getById(id1).__class__.__name__ == "Empty" assert am.getById(id0) == am.getByName("empty test asset 1")[0] am.destroy() assert len(am.assets) == 0 assert root.getChildren().getNumPaths() == 0
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