Exemple #1
0
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()
Exemple #2
0
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()
Exemple #3
0
 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]
Exemple #4
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())
Exemple #5
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 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]))
Exemple #8
0
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
Exemple #9
0
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
Exemple #10
0
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()
Exemple #11
0
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()
Exemple #12
0
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
Exemple #13
0
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)
Exemple #14
0
class CollisionChecker(object):
    """
    A fast collision checker that allows maximum 32 collision pairs
    author: weiwei
    date: 20201214osaka
    """
    def __init__(self, name="auto"):
        self.ctrav = CollisionTraverser()
        self.chan = CollisionHandlerQueue()
        self.np = NodePath(name)
        self.bitmask_list = [BitMask32(2**n) for n in range(31)]
        self._bitmask_ext = BitMask32(
            2**31)  # 31 is prepared for cd with external non-active objects
        self.all_cdelements = [
        ]  # a list of cdlnks or cdobjs for quick accessing the cd elements (cdlnks/cdobjs)

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

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

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

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

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

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

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

    def disable(self):
        """
        clear pairs and nodepath
        :return:
        """
        for cdelement in self.all_cdelements:
            cdelement['cdprimit_childid'] = -1
        self.all_cdelements = []
        for child in self.np.getChildren():
            child.removeNode()
        self.bitmask_list = list(range(31))
Exemple #15
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
    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
Exemple #17
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
Exemple #18
0
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