Exemple #1
0
class NodeConnector:
    def __init__(self, socketA, socketB):
        self.connectorID = uuid4()
        self.socketA = socketA
        self.socketB = socketB
        self.line = LineNodePath(ShowBaseGlobal.aspect2d,
                                 thickness=2,
                                 colorVec=(0.8, 0.8, 0.8, 1))
        self.draw()

    def update(self):
        self.line.reset()
        self.draw()

    def draw(self):
        self.line.moveTo(self.socketA.plug.getPos(ShowBaseGlobal.aspect2d))
        self.line.drawTo(self.socketB.plug.getPos(ShowBaseGlobal.aspect2d))
        self.line.create()

    def has(self, socket):
        """Returns True if one of the sockets this connector connects is
        the given socket"""
        return socket == self.socketA or socket == self.socketB

    def connects(self, a, b):
        """Returns True if this connector connects socket a and b"""
        return (a == self.socketA or a
                == self.socketB) and (b == self.socketA or b == self.socketB)

    def disconnect(self):
        self.line.reset()
        self.socketA.setConnected(False)
        self.socketB.setConnected(False)
def draw_lines(lines: LineNodePath, *paths: dict, origin=None, relative=True):
    if origin is None:
        origin = lines.getCurrentPosition()
    lines.reset()
    for path in paths:
        if 'color' in path:
            lines.setColor(*path['color'])
        points = path['points']
        lines.moveTo(*origin)
        for point in points:
            if relative:
                point += origin
            lines.drawTo(*point)
    lines.create()
Exemple #3
0
class Simulation(ShowBase):
    scale = 1
    height = 500
    lateralError = 200
    dt = 0.1
    steps = 0
    #Test Controllers
    fuelPID = PID(10, 0.5, 10, 0, 100)
    heightPID = PID(0.08, 0, 0.3, 0.1, 1)
    pitchPID = PID(10, 0, 1000, -10, 10)
    rollPID = PID(10, 0, 1000, -10, 10)
    XPID = PID(0.2, 0, 0.8, -10, 10)
    YPID = PID(0.2, 0, 0.8, -10, 10)
    vulcain = NeuralNetwork()
    tau = 0.5
    Valves=[]

    CONTROL = False

    EMPTY = False
    LANDED = False
    DONE = False

    gimbalX = 0
    gimbalY = 0
    targetAlt = 150

    R = RE(200 * 9.806, 250 * 9.806, 7607000 / 9 * scale, 0.4)
    throttle = 0.0
    fuelMass_full = 417000 * scale
    fuelMass_init = 0.10

    radius = 1.8542 * scale
    length = 47 * scale
    Cd = 1.5


    def __init__(self, VISUALIZE=False):

        self.VISUALIZE = VISUALIZE


        if VISUALIZE is True:
            ShowBase.__init__(self)
            self.setBackgroundColor(0.2, 0.2, 0.2, 1)
            self.setFrameRateMeter(True)
            self.render.setShaderAuto()
            self.cam.setPos(-120 * self.scale, -120 * self.scale, 120 * self.scale)
            self.cam.lookAt(0, 0, 10 * self.scale)
            alight = AmbientLight('ambientLight')
            alight.setColor(Vec4(0.5, 0.5, 0.5, 1))
            alightNP = self.render.attachNewNode(alight)

            dlight = DirectionalLight('directionalLight')
            dlight.setDirection(Vec3(1, -1, -1))
            dlight.setColor(Vec4(0.7, 0.7, 0.7, 1))
            dlightNP = self.render.attachNewNode(dlight)

            self.render.clearLight()
            self.render.setLight(alightNP)
            self.render.setLight(dlightNP)

            self.accept('escape', self.doExit)
            self.accept('r', self.doReset)
            self.accept('f1', self.toggleWireframe)
            self.accept('f2', self.toggleTexture)
            self.accept('f3', self.toggleDebug)
            self.accept('f5', self.doScreenshot)

            inputState.watchWithModifiers('forward', 'w')
            inputState.watchWithModifiers('left', 'a')
            inputState.watchWithModifiers('reverse', 's')
            inputState.watchWithModifiers('right', 'd')
            inputState.watchWithModifiers('turnLeft', 'q')
            inputState.watchWithModifiers('turnRight', 'e')
            self.ostData = OnscreenText(text='ready', pos=(-1.3, 0.9), scale=0.07, fg=Vec4(1, 1, 1, 1),
            align=TextNode.ALeft)

        self.fuelMass = self.fuelMass_full * self.fuelMass_init
        self.vulcain.load_existing_model(path="LandingRockets/",model_name="140k_samples_1024neurons_3layers_l2-0.000001")



        # Physics
        self.setup()

    # _____HANDLER_____

    def doExit(self):
        self.cleanup()
        sys.exit(1)

    def doReset(self):
        self.cleanup()
        self.setup()

    def toggleWireframe(self):
        ...#self.toggleWireframe()

    def toggleTexture(self):
        ...#self.toggleTexture()

    def toggleDebug(self):
        if self.debugNP.isHidden():
            self.debugNP.show()
        else:
            self.debugNP.hide()

    def doScreenshot(self):
        self.screenshot('Bullet')

    # ____TASK___

    def processInput(self):
        throttleChange = 0.0

        if inputState.isSet('forward'): self.gimbalY = 10
        if inputState.isSet('reverse'): self.gimbalY = -10
        if inputState.isSet('left'):    self.gimbalX = 10
        if inputState.isSet('right'):   self.gimbalX = -10
        if inputState.isSet('turnLeft'):  throttleChange = -1.0
        if inputState.isSet('turnRight'): throttleChange = 1.0

        self.throttle += throttleChange / 100.0
        self.throttle = min(max(self.throttle, 0), 1)

    def processContacts(self):
        result = self.world.contactTestPair(self.rocketNP.node(), self.groundNP.node())
        #print(result.getNumContacts())
        if result.getNumContacts() != 0:
            self.LANDED = True
            #self.DONE = True

    def update(self,task):

        #self.control(0.1,0.1,0.1)
        #self.processInput()
        #self.processContacts()
        # self.terrain.update()
        return task.cont

    def cleanup(self):
        self.world.removeRigidBody(self.groundNP.node())
        self.world.removeRigidBody(self.rocketNP.node())
        self.world = None

        self.debugNP = None
        self.groundNP = None
        self.rocketNP = None

        self.worldNP.removeNode()

        self.LANDED = False
        self.EMPTY = False
        self.DONE = False
        self.steps = 0
        self.fuelMass = self.fuelMass_full*self.fuelMass_init

    def setup(self):

        self.targetAlt = r.randrange(100,300)
        self.Valves = np.array([0.15,0.2,0.15])
        self.EngObs = self.vulcain.predict_data_point(np.array(self.Valves).reshape(1,-1))

        if self.VISUALIZE is True:
            self.worldNP = self.render.attachNewNode('World')

        else:
            self.root = NodePath(PandaNode("world root"))
            self.worldNP = self.root.attachNewNode('World')






        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -9.80665))

        # World
        self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
        self.debugNP.node().showWireframe(True)
        self.debugNP.node().showConstraints(True)
        self.debugNP.node().showBoundingBoxes(False)
        self.debugNP.node().showNormals(True)
        self.debugNP.show()
        self.world.setDebugNode(self.debugNP.node())

        # self.debugNP.showTightBounds()
        # self.debugNP.showBounds()


        # Ground (static)
        shape = BulletPlaneShape(Vec3(0, 0, 1), 1)

        self.groundNP = self.worldNP.attachNewNode(BulletRigidBodyNode('Ground'))
        self.groundNP.node().addShape(shape)
        self.groundNP.setPos(0, 0, 0)
        self.groundNP.setCollideMask(BitMask32.allOn())

        self.world.attachRigidBody(self.groundNP.node())

        # Rocket
        shape = BulletCylinderShape(self.radius, self.length, ZUp)

        self.rocketNP = self.worldNP.attachNewNode(BulletRigidBodyNode('Cylinder'))
        self.rocketNP.node().setMass(27200 * self.scale)
        self.rocketNP.node().addShape(shape)
        #self.rocketNP.setPos(20,20,250)
        self.rocketNP.setPos(20, 20, r.randrange(100, 300)+200)
        #self.rocketNP.setPos(r.randrange(-self.lateralError, self.lateralError, 1), r.randrange(-self.lateralError, self.lateralError, 1), self.height)
        # self.rocketNP.setPos(0, 0, self.length*10)
        self.rocketNP.setCollideMask(BitMask32.allOn())
        # self.rocketNP.node().setCollisionResponse(0)
        self.rocketNP.node().notifyCollisions(True)

        self.world.attachRigidBody(self.rocketNP.node())

        for i in range(4):
            leg = BulletCylinderShape(0.1 * self.radius, 0.5 * self.length, XUp)
            self.rocketNP.node().addShape(leg, TransformState.makePosHpr(
                Vec3(6 * self.radius * math.cos(i * math.pi / 2), 6 * self.radius * math.sin(i * math.pi / 2),
                     -0.6 * self.length), Vec3(i * 90, 0, 30)))

        shape = BulletConeShape(0.75 * self.radius, 0.5 * self.radius, ZUp)
        self.rocketNP.node().addShape(shape, TransformState.makePosHpr(Vec3(0, 0, -1 / 2 * self.length), Vec3(0, 0, 0)))

        # Fuel
        self.fuelRadius = 0.9 * self.radius
        shape = BulletCylinderShape(self.fuelRadius, 0.01 * self.length, ZUp)
        self.fuelNP = self.worldNP.attachNewNode(BulletRigidBodyNode('Cone'))
        self.fuelNP.node().setMass(self.fuelMass_full * self.fuelMass_init)
        self.fuelNP.node().addShape(shape)
        self.fuelNP.setPos(0, 0, self.rocketNP.getPos().getZ() - self.length * 0.5 * (1 - self.fuelMass_init))
        self.fuelNP.setCollideMask(BitMask32.allOn())
        self.fuelNP.node().setCollisionResponse(0)

        self.world.attachRigidBody(self.fuelNP.node())

        frameA = TransformState.makePosHpr(Point3(0, 0, 0), Vec3(0, 0, 90))
        frameB = TransformState.makePosHpr(Point3(0, 0, 0), Vec3(0, 0, 90))

        self.fuelSlider = BulletSliderConstraint(self.rocketNP.node(), self.fuelNP.node(), frameA, frameB, 1)
        self.fuelSlider.setTargetLinearMotorVelocity(0)
        self.fuelSlider.setDebugDrawSize(2.0)
        self.fuelSlider.set_lower_linear_limit(0)
        self.fuelSlider.set_upper_linear_limit(0)
        self.world.attachConstraint(self.fuelSlider)

        self.npThrustForce = LineNodePath(self.rocketNP, 'Thrust', thickness=4, colorVec=Vec4(1, 0.5, 0, 1))
        self.npDragForce = LineNodePath(self.rocketNP, 'Drag', thickness=4, colorVec=Vec4(1, 0, 0, 1))
        self.npLiftForce = LineNodePath(self.rocketNP, 'Lift', thickness=4, colorVec=Vec4(0, 0, 1, 1))
        self.npFuelState = LineNodePath(self.fuelNP, 'Fuel', thickness=20, colorVec=Vec4(0, 1, 0, 1))

        self.rocketCSLon = self.radius ** 2 * math.pi
        self.rocketCSLat = self.length * 2 * self.radius

        if self.VISUALIZE is True:
            self.terrain = loader.loadModel("LZGrid2.egg")
            self.terrain.setScale(10)
            self.terrain.reparentTo(self.render)
            self.terrain.setColor(Vec4(0.1, 0.2, 0.1, 1))
            self.toggleTexture()

        #self.fuelNP.setPos(0, 0, self.rocketNP.getPos().getZ() - self.length * 0.4 * (1 - self.fuelMass_init))

        for i in range(5):
            self.world.doPhysics(self.dt, 5, 1.0 / 180.0)

        self.fuelSlider.set_lower_linear_limit(-self.length * 0.5 * (1 - self.fuelMass_init))
        self.fuelSlider.set_upper_linear_limit(self.length * 0.5 * (1 - self.fuelMass_init))

        for i in range(100):
            self.world.doPhysics(self.dt, 5, 1.0 / 180.0)
            self.rocketNP.node().applyForce(Vec3(0,0,300000), Vec3(0, 0, 0))








    def updateRocket(self, mdot, dt):

        # Fuel Update
        self.fuelMass = self.fuelNP.node().getMass() - dt * mdot
        if self.fuelMass <= 0:
            self.EMPTY is True
        fuel_percent = self.fuelMass / self.fuelMass_full
        self.fuelNP.node().setMass(self.fuelMass)
        fuelHeight = self.length * fuel_percent
        I1 = 1 / 2 * self.fuelMass * self.fuelRadius ** 2
        I2 = 1 / 4 * self.fuelMass * self.fuelRadius ** 2 + 1 / 12 * self.fuelMass * fuelHeight * fuelHeight
        self.fuelNP.node().setInertia(Vec3(I2, I2, I1))

        # Shift fuel along slider constraint
        fuelTargetPos = 0.5 * (self.length - fuelHeight)
        fuelPos = self.fuelSlider.getLinearPos()
        self.fuelSlider.set_upper_linear_limit(fuelTargetPos)
        self.fuelSlider.set_lower_linear_limit(-fuelTargetPos)

        self.npFuelState.reset()
        self.npFuelState.drawArrow2d(Vec3(0, 0, -0.5 * fuelHeight), Vec3(0, 0, 0.5 * fuelHeight), 45, 2)
        self.npFuelState.create()

    def observe(self):
        pos = self.rocketNP.getPos()
        vel = self.rocketNP.node().getLinearVelocity()
        quat = self.rocketNP.getTransform().getQuat()
        Roll, Pitch, Yaw = quat.getHpr()
        rotVel = self.rocketNP.node().getAngularVelocity()
        offset = self.targetAlt-pos.getZ()

        return pos, vel, Roll, Pitch, Yaw, rotVel, self.fuelMass / self.fuelMass_full, self.EMPTY, self.DONE, self.LANDED, offset, self.EngObs[0], self.Valves

    def control(self,ValveCommands):

        self.gimbalX = 0
        self.gimbalY = 0

        self.Valves = ValveCommands-(ValveCommands-self.Valves)*np.exp(-self.dt/self.tau)

        self.EngObs = self.vulcain.predict_data_point(np.array(self.Valves).reshape(1,-1 ))
        #Brennkammerdruck, Gaskammertemp, H2Massenstrom, LOX MAssentrom, Schub
        #Bar,K,Kg/s,Kg/s,kN
        #self.dt = globalClock.getDt()

        pos = self.rocketNP.getPos()
        vel = self.rocketNP.node().getLinearVelocity()
        quat = self.rocketNP.getTransform().getQuat()
        Roll, Pitch, Yaw = quat.getHpr()
        rotVel = self.rocketNP.node().getAngularVelocity()

        # CHECK STATE
        if self.fuelMass <= 0:
            self.EMPTY = True
        #if pos.getZ() <= 36:
        #    self.LANDED = True
        self.LANDED = False
        self.processContacts()

        P, T, rho = air_dens(pos[2])
        rocketZWorld = quat.xform(Vec3(0, 0, 1))

        AoA = math.acos(min(max(dot(norm(vel), norm(-rocketZWorld)), -1), 1))

        dynPress = 0.5 * dot(vel, vel) * rho

        dragArea = self.rocketCSLon + (self.rocketCSLat - self.rocketCSLon) * math.sin(AoA)

        drag = norm(-vel) * dynPress * self.Cd * dragArea

        time = globalClock.getFrameTime()

        liftVec = norm(vel.project(rocketZWorld) - vel)
        if AoA > 0.5 * math.pi:
            liftVec = -liftVec
        lift = liftVec * (math.sin(AoA * 2) * self.rocketCSLat * dynPress)

        if self.CONTROL:
            self.throttle = self.heightPID.control(pos.getZ(), vel.getZ(), 33)

            pitchTgt = self.XPID.control(pos.getX(), vel.getX(), 0)
            self.gimbalX = -self.pitchPID.control(Yaw, rotVel.getY(), pitchTgt)

            rollTgt = self.YPID.control(pos.getY(), vel.getY(), 0)
            self.gimbalY = -self.rollPID.control(Pitch, rotVel.getX(), -rollTgt)



        self.thrust = self.EngObs[0][4]*1000
        #print(self.EngObs)
        quat = self.rocketNP.getTransform().getQuat()
        quatGimbal = TransformState.makeHpr(Vec3(0, self.gimbalY, self.gimbalX)).getQuat()
        thrust = quatGimbal.xform(Vec3(0, 0, self.thrust))
        thrustWorld = quat.xform(thrust)

        #print(thrustWorld)

        self.npDragForce.reset()
        self.npDragForce.drawArrow2d(Vec3(0, 0, 0), quat.conjugate().xform(drag) / 1000, 45, 2)
        self.npDragForce.create()

        self.npLiftForce.reset()
        self.npLiftForce.drawArrow2d(Vec3(0, 0, 0), quat.conjugate().xform(lift) / 1000, 45, 2)
        self.npLiftForce.create()

        self.npThrustForce.reset()
        if self.EMPTY is False & self.LANDED is False:
            self.npThrustForce.drawArrow2d(Vec3(0, 0, -0.5 * self.length),
                                           Vec3(0, 0, -0.5 * self.length) - thrust / 30000, 45, 2)
            self.npThrustForce.create()

        self.rocketNP.node().applyForce(drag, Vec3(0, 0, 0))
        self.rocketNP.node().applyForce(lift, Vec3(0, 0, 0))
        #print(self.EMPTY,self.LANDED)
        if self.EMPTY is False & self.LANDED is False:
            self.rocketNP.node().applyForce(thrustWorld, quat.xform(Vec3(0, 0, -1 / 2 * self.length)))
            self.updateRocket(self.EngObs[0][2]+self.EngObs[0][3], self.dt)
        self.rocketNP.node().setActive(True)
        self.fuelNP.node().setActive(True)

        self.processInput()

        self.world.doPhysics(self.dt)
        self.steps+=1


        if self.steps > 1000:
            self.DONE = True

        telemetry = []

        telemetry.append('Thrust: {}'.format(int(self.EngObs[0][4])))
        telemetry.append('Fuel: {}%'.format(int(self.fuelMass / self.fuelMass_full * 100.0)))
        telemetry.append('Gimbal: {}'.format(int(self.gimbalX)) + ',{}'.format(int(self.gimbalY)))
        telemetry.append('AoA: {}'.format(int(AoA / math.pi * 180.0)))
        telemetry.append('\nPos: {},{}'.format(int(pos.getX()), int(pos.getY())))
        telemetry.append('Height: {}'.format(int(pos.getZ())))
        telemetry.append('RPY: {},{},{}'.format(int(Roll), int(Pitch), int(Yaw)))
        telemetry.append('Speed: {}'.format(int(np.linalg.norm(vel))))
        telemetry.append('Vel: {},{},{}'.format(int(vel.getX()), int(vel.getY()), int(vel.getZ())))
        telemetry.append('Rot: {},{},{}'.format(int(rotVel.getX()), int(rotVel.getY()), int(rotVel.getZ())))
        telemetry.append('LANDED: {}'.format(self.LANDED))
        telemetry.append('Time: {}'.format(self.steps*self.dt))
        telemetry.append('TARGET: {}'.format(self.targetAlt))
        #print(pos)
        if self.VISUALIZE is True:
            self.ostData.setText('\n'.join(telemetry))
            self.cam.setPos(pos[0] - 120 * self.scale, pos[1] - 120 * self.scale, pos[2] + 80 * self.scale)
            self.cam.lookAt(pos[0], pos[1], pos[2])
            self.taskMgr.step()


    """
class LabTask03(DirectObject):
  
  #define the state of the game and level
  gameState = 'INIT'
  gameLevel = 1
  cameraState = 'STARTUP'
  count = 0
  attempts = 3
  posX = -200
  posY = 20
  posZ = 30
  score = 0
  contacts = 0
  pause = False
  fire = True
  desiredCamPos = Vec3(-200,30,20)

  def __init__(self):
    self.imageObject = OnscreenImage(image = 'models/splashscreen.png', pos=(0,0,0), scale=(1.4,1,1))
    preloader = Preloader()
    self.musicLoop = loader.loadSfx("music/loop/EndlessBliss.mp3")
    self.snowmansHit = loader.loadSfx("music/effects/snowball_hit.wav")
    self.candleThrow = loader.loadSfx("music/effects/snowball_throw.wav")
    self.presentHit = loader.loadSfx("music/effects/present_hit.wav")
    self.loseSound = loader.loadSfx("music/effects/Failure-WahWah.mp3")
    self.winSound = loader.loadSfx("music/effects/Ta Da-SoundBible.com-1884170640.mp3")
    self.nextLevelSound = loader.loadSfx("music/effects/button-17.wav")
    self.loseScreen = OnscreenImage(image = 'models/losescreen.png', pos=(0,0,0), scale=(1.4,1,1))
    self.loseScreen.hide()
    self.winScreen = OnscreenImage(image = 'models/winscreen.png', pos=(0,0,0), scale=(1.4,1,1))
    self.winScreen.hide()
    self.helpScreen = OnscreenImage(image = 'models/helpscreen.jpg', pos=(0,0,0.1), scale=(1,1,0.8))
    self.helpScreen.hide()
    self.backBtn = DirectButton(text=("Back"), scale = 0.1,  pos = (0,0,-0.8), command = self.doBack)
    self.retryBtn = DirectButton(text="Retry", scale = 0.1, pos = (0,0,0), command = self.doRetry)
    self.retryBtn.hide()
    self.menuBtn = DirectButton(text="Main Menu", scale = 0.1, pos = (0,0,0), command = self.doBack)
    self.menuBtn.hide()
    self.backBtn.hide()
    base.setBackgroundColor(0.1, 0.1, 0.8, 1)
    #base.setFrameRateMeter(True)
    
    # Position the camera
    base.cam.setPos(0, 30, 20)
    base.cam.lookAt(0, 30, 0)

    # Light
    alight = AmbientLight('ambientLight')
    alight.setColor(Vec4(0.5, 0.5, 0.5, 1))
    alightNP = render.attachNewNode(alight)

    dlight = DirectionalLight('directionalLight')
    dlight.setDirection(Vec3(1, 1, -1))
    dlight.setColor(Vec4(0.7, 0.7, 0.7, 1))
    dlightNP = render.attachNewNode(dlight)

    render.clearLight()
    render.setLight(alightNP)
    render.setLight(dlightNP)

    # Input
    self.accept('escape', self.doExit)
    self.accept('r', self.doReset)
    self.accept('f1', self.toggleWireframe)
    self.accept('f2', self.toggleTexture)
    self.accept('f3', self.toggleDebug)
    self.accept('f5', self.doScreenshot)
    self.accept('f', self.doShoot, [True])
    self.accept('p', self.doPause)

    inputState.watchWithModifiers('forward', 'w')
    inputState.watchWithModifiers('left', 'a')
    inputState.watchWithModifiers('reverse', 's')
    inputState.watchWithModifiers('right', 'd')
    inputState.watchWithModifiers('turnLeft', 'q')
    inputState.watchWithModifiers('turnRight', 'e')
    
    inputState.watchWithModifiers('moveLineUp', 'i')
    inputState.watchWithModifiers('moveLineDown','k')
    inputState.watchWithModifiers('moveLineRight','l')
    inputState.watchWithModifiers('moveLineLeft','j')
    
    self.font = loader.loadFont('models/SHOWG.TTF')
    self.font.setPixelsPerUnit(60)
    self.attemptText = OnscreenText(text='', pos = (0.9,0.8), scale = 0.07, font = self.font)
    self.levelText = OnscreenText(text='', pos=(-0.9,0.9), scale = 0.07, font = self.font )
    self.scoreText = OnscreenText(text='', pos = (0.9,0.9), scale = 0.07, font = self.font)
    self.text = OnscreenText(text = '', 
                              pos = (0, 0), scale = 0.07, font = self.font)
    self.pauseText = OnscreenText(text='P: Pause', pos= (0.9,0.7), scale = 0.05, font = self.font)
    self.pauseText.hide()
    # Task
    taskMgr.add(self.update, 'updateWorld')

    # Physics
    self.setup()

  # _____HANDLER_____
  
  def doRetry(self):
    self.loseScreen.hide()
    self.levelText.clearText()
    self.scoreText.clearText()
    self.attemptText.clearText()
    self.playGame()
    self.retryBtn.hide()
    self.cleanup()
    self.setup()
    

  def doExit(self):
    self.cleanup()
    sys.exit(1)

  def doReset(self):
    self.attempts = 3
    self.cameraState = 'STARTUP'
    base.cam.setPos(0,30,20)
    self.arrow.removeNode()
    self.scoreText.clearText()
    self.levelText.clearText()
    self.attemptText.clearText()
    self.cleanup()
    self.setup()
    
  def toggleWireframe(self):
    base.toggleWireframe()

  def toggleTexture(self):
    base.toggleTexture()

  def toggleDebug(self):
    if self.debugNP.isHidden():
      self.debugNP.show()
    else:
      self.debugNP.hide()

  def doScreenshot(self):
    base.screenshot('Bullet')
    
    
  def doShoot(self, ccd):
    if(self.fire and self.attempts > 0 and self.gameState == 'PLAY'):
      self.cameraState = 'LOOK'
      self.fire = False
      self.candleThrow.play()
      self.attempts -= 1
      #get from/to points from mouse click
      ## pMouse = base.mouseWatcherNode.getMouse()
      ## pFrom = Point3()
      ## pTo = Point3()
      ## base.camLens.extrude(pMouse, pFrom, pTo)
      
      ## pFrom = render.getRelativePoint(base.cam, pFrom)
      ## pTo = render.getRelativePoint(base.cam, pTo)
      
      # calculate initial velocity
      v = self.pTo - self.pFrom
      ratio = v.length() / 40
      v.normalize()
      v *= 70.0 * ratio
      torqueOffset = random.random() * 10
      
      #create bullet
      shape = BulletSphereShape(0.5)
      shape01 = BulletSphereShape(0.5)
      shape02 = BulletSphereShape(0.5)
      shape03 = BulletSphereShape(0.5)
      body = BulletRigidBodyNode('Candle')
      bodyNP = self.worldNP.attachNewNode(body)
      bodyNP.node().addShape(shape, TransformState.makePos(Point3(0,0,0)))
      bodyNP.node().addShape(shape01, TransformState.makePos(Point3(0,0.5,-0.5)))
      bodyNP.node().addShape(shape02,TransformState.makePos(Point3(0,1,-1)))
      bodyNP.node().addShape(shape03,TransformState.makePos(Point3(0,1.5,-1.5)))
      bodyNP.node().setMass(100)
      bodyNP.node().setFriction(1.0)
      bodyNP.node().setLinearVelocity(v)
      bodyNP.node().applyTorque(v*torqueOffset)
      bodyNP.setPos(self.pFrom)
      bodyNP.setCollideMask(BitMask32.allOn())
      
      visNP = loader.loadModel('models/projectile.X')
      visNP.setScale(0.7)
      visNP.clearModelNodes()
      visNP.reparentTo(bodyNP)
      
      #self.bird = bodyNP.node()
      
      if ccd:
          bodyNP.node().setCcdMotionThreshold(1e-7)
          bodyNP.node().setCcdSweptSphereRadius(0.5)
          
      self.world.attachRigidBody(bodyNP.node())
      
      #remove the bullet again after 1 sec
      self.bullets = bodyNP
      taskMgr.doMethodLater(5, self.removeBullet, 'removeBullet', extraArgs=[bodyNP], 
                            appendTask = True)
      
    
  def removeBullet(self, bulletNP, task):
    self.cameraState = 'STAY'
    self.fire = True
    self.world.removeRigidBody(bulletNP.node())
    bulletNP.removeNode()
    if(self.attempts <= 0 and len(self.snowmans)>0):
      self.gameState = 'LOSE'
      self.doContinue()
      
    return task.done

  # ____TASK___

  def processInput(self, dt):
    force = Vec3(0, 0, 0)
    torque = Vec3(0, 0, 0)
    #print self.pTo.getY()
    if inputState.isSet('forward'):
      if(self.pTo.getZ() < 40):
        self.pTo.addZ(0.5)
    if inputState.isSet('reverse'):
      if(self.pTo.getZ() > 0 ):
        self.pTo.addZ(-0.5)
    if inputState.isSet('left'):
      if(self.pTo.getY() < 100):
        self.pTo.addY(0.5)
        self.arrow.setScale(self.arrow.getSx(),self.arrow.getSy()-0.006,self.arrow.getSz())
    if inputState.isSet('right'):
      if(self.pTo.getY() > 60):
        self.pTo.addY(-0.5)
        self.arrow.setScale(self.arrow.getSx(),self.arrow.getSy()+0.006,self.arrow.getSz())
        
    self.arrow.lookAt(self.pTo)
    
  def processContacts(self, dt):
      for box in self.boxes:
        for snowman in self.snowmans:
          result = self.world.contactTestPair(box, snowman.node())
      
          if (result.getNumContacts() > 0):
            self.snowmansHit.play()
            self.score += 100
            self.text.setPos(0,0.7)
            self.text.setText("HIT")
            self.snowmans.remove(snowman)
            self.world.removeRigidBody(snowman.node())
            snowman.removeNode()
            if(len(self.snowmans)  <= 0):
              self.gameState = "NEXT"
              self.text.setText('Nice! Press space to continue')

              
      for box in self.boxes:
        for present in self.presents:
          result01 = self.world.contactTestPair(box,present.node())
          if(result01.getNumContacts() > 0):
            self.presents.remove(present)
            self.world.removeRigidBody(present.node())
            present.removeNode()
            self.presentHit.play()
            self.score += 500
            
  def doContinue(self):
    if(self.gameState == 'INIT'):
      self.gameState = 'MENU'
      self.text.clearText()
      self.musicLoop.setLoop(True)
      self.musicLoop.setVolume(0.5)
      self.musicLoop.play()
      self.startBtn = DirectButton(text=("Play"), scale = 0.1, pos = (0,0,0),command=self.playGame)
      self.helpBtn = DirectButton(text=("Help"), scale = 0.1, pos = (0,0,-0.2),command=self.help)
      self.exitBtn = DirectButton(text=("Exit"), scale = 0.1,  pos = (0,0,-0.4), command = self.doExit)
      return
    
    if self.gameState == 'NEXT':
      self.nextLevelSound.play()
      self.fire = True
      self.gameLevel += 1
      self.score += (self.attempts * 100)
      self.doReset()
      self.gameState = 'PLAY'
      return
    
    if self.gameState == 'LOSE':
      self.loseSound.play()
      self.arrow.removeNode()
      self.loseScreen.show()
      self.levelText.hide()
      self.attemptText.hide()
      self.scoreText.hide()
      self.text.hide()
      self.pauseText.hide()
      self.retryBtn.show()
      return
    
    if self.gameState == 'WIN':
      self.levelText.hide()
      self.attemptText.hide()
      self.scoreText.clearText()
      self.scoreText.setPos(0,0.6)
      self.scoreText.setText("%s"%self.score)
      self.winScreen.show()
      self.winSound.play()
      self.menuBtn.show()
      self.pauseText.hide()
      return
      
    
  def playGame(self):
    print "Play Game"
    self.attempts = 3
    self.score = 0
    self.gameLevel = 1
    self.gameState = 'PLAY'
    self.musicLoop.setVolume(0.3)
    self.imageObject.hide()
    self.startBtn.hide()
    self.exitBtn.hide()
    self.helpBtn.hide()
    self.cleanup()
    self.setup()
    
  def doBack(self):
    self.gameState = 'MENU'
    self.scoreText.setPos(0.9,0.9)
    self.scoreText.hide()
    self.imageObject.show()
    self.startBtn.show()
    self.exitBtn.show()
    self.helpBtn.show()
    self.helpScreen.hide()
    self.backBtn.hide()
    self.menuBtn.hide()
    self.winScreen.hide()
    
  def help(self):
    self.gameState = 'HELP'
    self.startBtn.hide()
    self.exitBtn.hide()
    self.helpBtn.hide()
    self.backBtn.show()
    self.helpScreen.show()
    return
    
  def doPause(self):
    self.pause  = not self.pause
    if(self.pause):
      self.text.setText("Pause")
    else:
      self.text.clearText()
      

  def update(self, task):

    dt = globalClock.getDt()
    if(not self.pause):
      if self.gameState == 'INIT':
        self.text.setPos(0,0)
        self.text.setText('Press space to continue')
        self.accept('space',self.doContinue)
          
      if self.gameState == 'PLAY':
        #print self.posZ
        #if(self.posX < -120):
        #  self.posX += 0.03
        #  self.posZ -= 0.02
        #elif(self.posZ < 70):
        #  self.posZ += 0.02;
        #elif(self.posZ > 70 and self.posX > -120):
        #  self.posZ -= 0.02
        #  self.posX -= 0.03
        #base.cam.setPos(self.posX, self.posZ, self.posY)
        self.levelText.setText('Level: %s'%self.gameLevel)
        self.attemptText.setText('Tries Left: %s'%self.attempts)
        self.scoreText.setText('Score: %s'%self.score)
        self.pauseText.show()
        self.processContacts(dt)
        self.world.doPhysics(dt, 20, 1.0/180.0)
        #self.drawLines()
        self.processInput(dt)
        
        if self.cameraState == 'STARTUP':
          oldPos = base.cam.getPos()
          pos = (oldPos*0.9) + (self.desiredCamPos*0.1)
          base.cam.setPos(pos)
          base.cam.lookAt(0,30,0)
        elif self.cameraState == 'STAY':
          #oldPos = base.cam.getPos()
          #currPos = (oldPos*0.9) + (self.desiredCamPos*0.1)
          #base.cam.setPos(currPos)
          base.cam.lookAt(0,30,0)
        elif self.cameraState == 'LOOK':
          base.cam.lookAt(self.bullets)
          #base.cam.setPos(-200,self.bullets.getZ(),20)
        
      if self.gameState == 'NEXT':
        self.world.doPhysics(dt, 20, 1.0/180.0)
        

      ## self.raycast()
    
    return task.cont
      
  def raycast(self):
    # Raycast for closest hit
    tsFrom = TransformState.makePos(Point3(0,0,0))
    tsTo = TransformState.makePos(Point3(10,0,0))
    shape = BulletSphereShape(0.5)
    penetration = 1.0
    result = self.world.sweepTestClosest(shape, tsFrom, tsTo, penetration)
    if result.hasHit():
        torque = Vec3(10,0,0)
        force = Vec3(0,0,100)
        
        ## for hit in result.getHits():
            ## hit.getNode().applyTorque(torque)
            ## hit.getNode().applyCentralForce(force)
        result.getNode().applyTorque(torque)
        result.getNode().applyCentralForce(force)
        ## print result.getClosestHitFraction()
        ## print result.getHitFraction(), \
            ## result.getNode(), \
            ## result.getHitPos(), \
            ## result.getHitNormal()

  def cleanup(self):
    self.world = None
    self.worldNP.removeNode()
    self.arrow.removeNode()
    self.lines.reset()
    self.text.clearText()

  def setup(self):
    self.attemptText.show()
    self.levelText.show()
    self.scoreText.show()
    self.text.show()
    
    self.worldNP = render.attachNewNode('World')

    # World
    self.debugNP = self.worldNP.attachNewNode(BulletDebugNode('Debug'))
    self.debugNP.show()

    self.world = BulletWorld()
    self.world.setGravity(Vec3(0, 0, -9.81))
    self.world.setDebugNode(self.debugNP.node())
    
    #arrow
    self.scaleY = 10
    self.arrow = loader.loadModel('models/arrow.X')
    self.arrow.setScale(0.5,0.5,0.5)
    self.arrow.setAlphaScale(0.5)
    #self.arrow.setTransparency(TransparencyAttrib.MAlpha) 
    self.arrow.reparentTo(render)
    
    #SkyBox
    skybox = loader.loadModel('models/skybox.X')
    skybox.reparentTo(render)

    # Ground
    shape = BulletPlaneShape(Vec3(0, 0, 1), 0)

    np = self.worldNP.attachNewNode(BulletRigidBodyNode('Ground'))
    np.node().addShape(shape)
    np.setPos(0, 0, -1)
    np.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(np.node())
    
    visualNP = loader.loadModel('models/ground.X')
    visualNP.clearModelNodes()
    visualNP.reparentTo(np)
    
    #some boxes
    self.boxes = []
    self.snowmans = []
    self.platforms = []
    self.presents = []
    #TODO: Make a table
    #Table Top
    #self.createBox(Vec3(),Vec3())
    if(self.gameLevel == 1):
      self.createBox(Vec3(5,7,1),Vec3(0,5,10),1.0)
      #2 legs
      self.createBox(Vec3(4,1,4),Vec3(0,11,5),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-1,5),1.0)
      
      # Pigs
      self.createSnowman(2.0,Vec3(0, 5, 2.0),10.0)
      self.createSnowman(1.5, Vec3(0,-10,4.0),10.0)
      
    if(self.gameLevel == 2):
      #table01
      self.createBox(Vec3(5,14,1),Vec3(0,-2,12),2.0)
      self.createBox(Vec3(5,7,1),Vec3(0,5,10),2.0)
      self.createBox(Vec3(4,1,4),Vec3(0,11,5),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-1,5),1.0)
      #table02
      self.createBox(Vec3(5,7,1),Vec3(0,-9,10),2.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-3,5),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-15,5),1.0)
      #table03
      self.createBox(Vec3(1,1,1), Vec3(0,-1,14), 2.0)
      self.createBox(Vec3(1,1,1), Vec3(0,-3,14), 2.0)
      self.createBox(Vec3(1,1,1), Vec3(0,3,14), 2.0)
      self.createBox(Vec3(1,1,1), Vec3(0,-5,14), 2.0)
      self.createBox(Vec3(1,1,1), Vec3(0,5,14), 2.0)
      #pigs
      self.createSnowman(2.0,Vec3(0, 5, 2.0),10.0)
      self.createSnowman(2.0, Vec3(0,-9,2.0), 10.0)
      self.createSnowman(2.0,Vec3(0,-23,2.0),10.0)
      
    if(self.gameLevel == 3):
      #table01
      self.createBox(Vec3(4,2,2),Vec3(0,12,12),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,11,5),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,13,5),1.0)
      #table02
      self.createBox(Vec3(4,2,2),Vec3(0,-15,12),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-14,5),1.0)
      self.createBox(Vec3(4,1,4),Vec3(0,-16,5),1.0)
      #table03
      self.createBox(Vec3(4,10,1),Vec3(0,-1,12),1.0)
      self.createBox(Vec3(4,10,1),Vec3(0,-1,14),1.0)
      self.createBox(Vec3(4,2,4),Vec3(0,-2,5),0.1)
      #table04
      self.createPlatform(Vec3(4,8,1),Vec3(0,7,16),1.0)
      self.createPlatform(Vec3(4,8,1),Vec3(0,-9,16),1.0)
      self.createBox(Vec3(4,1,3),Vec3(0,13,20),1.0)
      self.createBox(Vec3(4,1,3),Vec3(0,-16,20),1.0)
      #table05
      self.createBox(Vec3(4,15,1),Vec3(0,-1,24),1.0)
      self.createStoneBox(Vec3(1,1,1),Vec3(0,2,20),5.0)
      self.createStoneBox(Vec3(1,1,1),Vec3(0,-2,20),5.0)
      self.createStoneBox(Vec3(1,1,1),Vec3(0,4,20),5.0)
      self.createStoneBox(Vec3(1,1,1),Vec3(0,8,20),5.0)
      self.createStoneBox(Vec3(1,1,1),Vec3(0,6,20),5.0)
      
      #pigs
      self.createSnowman(2.0,Vec3(0, 5, 2.0),10.0)
      self.createSnowman(2.0,Vec3(0,-8.5,2.0),10.0)
      self.createSnowman(1.5, Vec3(0,-9,19.5), 7.0)
      
      #presents
      self.createPresent(Vec3(2,2,2),Vec3(0,-20,5))
         
    if(self.gameLevel == 4):
      #wall
      self.createStoneBox(Vec3(4,1.5,10), Vec3(0,20,10),20)
      #table01
      self.createBox(Vec3(4,1,5), Vec3(0,7,7),1)
      self.createBox(Vec3(4,1,5), Vec3(0,0,7),1)
      self.createBox(Vec3(4,1,4), Vec3(0,3,7),1)
      self.createPlatform(Vec3(5,8,1), Vec3(0,4,13),1)
      self.createBox(Vec3(4,1,3), Vec3(0,11,18),1)
      self.createBox(Vec3(4,1,3), Vec3(0,-3,18),1)
      self.createBox(Vec3(4,8,1), Vec3(0,4,25),1)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,4,27),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,7,27),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,2,27),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,2,29),2)
      #stairs
      self.createPlatform(Vec3(4,50,4), Vec3(0,-55,5),100)
      #table02
      self.createBox(Vec3(4,1,5), Vec3(0,-13,15),1)
      self.createBox(Vec3(4,1,5), Vec3(0,-20,15),1)
      self.createBox(Vec3(4,1,4), Vec3(0,-17,15),1)
      self.createPlatform(Vec3(4,8,1), Vec3(0,-16,22),1)
      self.createBox(Vec3(4,1,3), Vec3(0,-9,28),1)
      self.createBox(Vec3(4,1,3), Vec3(0,-23,28),1)
      self.createBox(Vec3(4,8,1), Vec3(0,-16,33),1)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,-16,35),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,-13,35),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,-18,35),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,-18,37),2)
      self.createStoneBox(Vec3(1,1,1), Vec3(0,-14,37),2)
      
      #snowman
      self.createSnowman(2.0,Vec3(0,30,5),1.0)
      self.createSnowman(1.5,Vec3(0,4,18),1.0)
      self.createSnowman(1.5,Vec3(0,-13,26),1.0)
      self.createSnowman(1.5,Vec3(0,-19,26),1.0)
      self.createSnowman(2.0,Vec3(0,12,5),1.0)
      self.createPresent(Vec3(2,2,2),Vec3(0,-25,13))
      self.createPresent(Vec3(3,3,3),Vec3(0,-30,13))
      self.createPresent(Vec3(4,4,4),Vec3(0,-36,13))
      #self.createSnowman(1.5,Vec3(0,4,20),1.0)

      
    if(self.gameLevel == 5):
      #table01
      self.createStoneBox(Vec3(4,7,3), Vec3(0,30,5),10.0)
      self.createStoneBox(Vec3(4,7,3), Vec3(0,-30,5),10.0)
      self.createBox(Vec3(4,1,3), Vec3(0,0,5), 1.0)
      self.createSnowman(1.5,Vec3(0,-6,5),1.0)
      self.createSnowman(1.5,Vec3(0,6,5),1.0)
      self.createBox(Vec3(4,1,3), Vec3(0,-12,5), 1.0)
      self.createBox(Vec3(4,1,3), Vec3(0,12,5), 1.0)
      self.createSnowman(1.5,Vec3(0,-18,5),1.0)
      self.createSnowman(1.5,Vec3(0,18,5),1.0)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,-18,10), 0.1)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,-6,10), 0.1)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,6,10), 0.1)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,18,10), 0.1)
      self.createStoneBox(Vec3(4,1,3),Vec3(0,23,14), 1.0)
      self.createStoneBox(Vec3(4,1,3),Vec3(0,-23,14), 1.0)
      self.createBox(Vec3(4,1,3),Vec3(0,18,14),1.0)
      self.createBox(Vec3(4,1,3),Vec3(0,-18,14),1.0)
      self.createStoneBox(Vec3(4,1,7),Vec3(0,13,20), 2.0)
      self.createStoneBox(Vec3(4,1,7),Vec3(0,-13,20), 2.0)
      self.createBox(Vec3(4,1,3),Vec3(0,8,14),1.0)
      self.createBox(Vec3(4,1,3),Vec3(0,-8,14),1.0)
      self.createStoneBox(Vec3(4,1,3),Vec3(0,3,14), 1.0)
      self.createStoneBox(Vec3(4,1,3),Vec3(0,-3,14), 1.0)
      self.createPlatform(Vec3(4,3.5,1),Vec3(0,-20 ,20), 1.0)
      self.createPlatform(Vec3(4,3.5,1),Vec3(0,20 ,20), 1.0)
      self.createPlatform(Vec3(4,3.5,1),Vec3(0,-5 ,20), 1.0)
      self.createPlatform(Vec3(4,3.5,1),Vec3(0,5 ,20), 1.0)
      self.createStoneBox(Vec3(4,1,3.5),Vec3(0,-18,25), 2.0)
      self.createStoneBox(Vec3(4,1,3.5),Vec3(0,18,25), 2.0)
      self.createStoneBox(Vec3(4,1,3.5),Vec3(0,-7.5,25), 2.0)
      self.createStoneBox(Vec3(4,1,3.5),Vec3(0,7.5,25), 2.0)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,-12,30), 2.0)
      self.createStoneBox(Vec3(4,6,1),Vec3(0,12,30), 2.0)
      self.createBox(Vec3(4,1,5),Vec3(0,-5,30), 2.0)
      self.createBox(Vec3(4,1,5),Vec3(0,5,30), 2.0)
      self.createBox(Vec3(4,5,1),Vec3(0,0,40), 2.0)
      self.createPlatform(Vec3(4,2,0.5),Vec3(0,0,42), 2.0)
      self.createStoneBox(Vec3(4,0.5,2),Vec3(0,-3.5,45), 2.0)
      self.createStoneBox(Vec3(4,0.5,2),Vec3(0,3.5,45), 2.0)
      self.createStoneBox(Vec3(4,4,0.5),Vec3(0,0,48), 2.0)
      
      self.createPresent(Vec3(1.5,1.5,1.5),Vec3(0,22,30))
      self.createPresent(Vec3(1.5,1.5,1.5),Vec3(0,-22,30))
      self.createPresent(Vec3(2,2,1),Vec3(0,0,44))
      self.createPresent(Vec3(3,3,4),Vec3(0,0,33))
      
      
    if(self.gameLevel > 5):
      self.gameState = 'WIN'
      self.doContinue()
      return
        
    # drawing lines between the points 
    ## self.lines = LineNodePath(parent = render, thickness = 3.0, colorVec = Vec4(1, 0, 0, 1))
    ## self.pFrom = Point3(-4, 0, 0.5)
    ## self.pTo = Point3(4, 0, 0.5)
    
    # Aiming line 
    self.lines = LineNodePath(parent = render, thickness = 3.0, 
                              colorVec = Vec4(1, 0, 0, 1))
    self.pFrom = Point3(0, 100, 0.5)
    self.pTo = Point3(0, 60, 10)
    
    self.arrow.setPos(self.pFrom)
      
    
  def drawLines(self):  
    # Draws lines for the ray.
    self.lines.reset()  
    self.lines.drawLines([(self.pFrom,self.pTo)])
    self.lines.create()
    
  def createBox(self,size,pos,mass):
    shape = BulletBoxShape(size)
    body = BulletRigidBodyNode('Obstacle')
    bodyNP = self.worldNP.attachNewNode(body)
    bodyNP.node().addShape(shape)
    bodyNP.node().setMass(mass)
    bodyNP.node().setFriction(1.0)
    bodyNP.node().setDeactivationEnabled(False)
    bodyNP.setPos(pos)
    bodyNP.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(bodyNP.node())
            
    visNP = loader.loadModel('models/crate.X')
    visNP.setScale(size*2)
    visNP.clearModelNodes()
    visNP.reparentTo(bodyNP)
    self.boxes.append(body)
    
  def createStoneBox(self,size,pos,mass):
    shape = BulletBoxShape(size)
    body = BulletRigidBodyNode('Obstacle')
    bodyNP = self.worldNP.attachNewNode(body)
    bodyNP.node().addShape(shape)
    bodyNP.node().setMass(mass)
    bodyNP.node().setFriction(1.0)
    bodyNP.node().setDeactivationEnabled(False)
    bodyNP.setPos(pos)
    bodyNP.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(bodyNP.node())
            
    visNP = loader.loadModel('models/stone.X')
    visNP.setScale(size*2)
    visNP.clearModelNodes()
    visNP.reparentTo(bodyNP)
    self.boxes.append(body)
    
  def createSnowman(self, size, pos, mass):
    shape = BulletBoxShape(Vec3(size,size,size))
    shape01 = BulletSphereShape(size/2)
    body = BulletRigidBodyNode('Snowman')
    np = self.worldNP.attachNewNode(body)
    np.node().setMass(mass)
    np.node().addShape(shape, TransformState.makePos(Point3(0,0,-1)))
    np.node().addShape(shape01, TransformState.makePos(Point3(0,0,1)))
    np.node().setFriction(10.0)
    np.node().setDeactivationEnabled(False)
    np.setPos(pos)
    np.setCollideMask(BitMask32.allOn())
      
    self.world.attachRigidBody(np.node())
  
    visualNP = loader.loadModel('models/snowman.X')
    visualNP.setScale(size)
    visualNP.clearModelNodes()
    visualNP.reparentTo(np)
    self.snowmans.append(np)
    
  def createPlatform(self,size,pos,mass):
    shape = BulletBoxShape(size)
    body = BulletRigidBodyNode('Platform')
    bodyNP = self.worldNP.attachNewNode(body)
    bodyNP.node().addShape(shape)
    bodyNP.node().setMass(mass)
    bodyNP.node().setFriction(1.0)
    bodyNP.node().setDeactivationEnabled(False)
    bodyNP.setPos(pos)
    bodyNP.setCollideMask(BitMask32.allOn())

    self.world.attachRigidBody(bodyNP.node())
            
    visNP = loader.loadModel('models/crate.X')
    visNP.setScale(size*2)
    visNP.clearModelNodes()
    visNP.reparentTo(bodyNP)
    self.platforms.append(body)
    
  def createPresent(self,size,pos):
    shape = BulletBoxShape(size*0.7)
    body = BulletRigidBodyNode('Present')
    bodyNP = self.worldNP.attachNewNode(body)
    bodyNP.node().addShape(shape)
    bodyNP.node().setMass(1.0)
    bodyNP.node().setFriction(1.0)
    bodyNP.node().setDeactivationEnabled(False)
    bodyNP.setPos(pos)
    bodyNP.setCollideMask(BitMask32.allOn())
    
    self.world.attachRigidBody(bodyNP.node())
    
    visNP = loader.loadModel('models/present.X')
    visNP.setScale(size*2)
    visNP.clearModelNodes()
    visNP.reparentTo(bodyNP)
    self.presents.append(bodyNP)
class DirectDiagram(DirectFrame):
    def __init__(self, parent=None, **kw):
        optiondefs = (
            # Define type of DirectGuiWidget
            ('data', [], self.refresh),
            ('numPosSteps', 0, self.refresh),
            ('numPosStepsStep', 1, self.refresh),
            ('numNegSteps', 0, self.refresh),
            ('numNegStepsStep', 1, self.refresh),
            ('numtextScale', 0.05, self.refresh),
            ('showDataNumbers', False, self.refresh),
            ('dataNumtextScale', 0.05, self.refresh),
            ('stepAccuracy', 2, self.refresh),
            ('stepFormat', float, self.refresh),
            ('numberAreaWidth', 0.15, self.refresh),
            #('numStates',      1,           None),
            #('state',          DGG.NORMAL,  None),
        )
        # Merge keyword options with default options
        self.defineoptions(kw, optiondefs)

        self.lines = None
        self.measureLines = None
        self.centerLine = None
        self.xDescriptions = []
        self.points = []

        # Initialize superclasses
        DirectFrame.__init__(self, parent)

        # Call option initialization functions
        self.initialiseoptions(DirectDiagram)

    def refresh(self):
        # sanity check so we don't get here to early
        if not hasattr(self, "bounds"): return
        self.frameInitialiseFunc()

        textLeftSizeArea = self['numberAreaWidth']
        # get the left and right edge of our frame
        left = DGH.getRealLeft(self)
        right = DGH.getRealRight(self)
        diagramLeft = left + textLeftSizeArea

        xStep = (DGH.getRealWidth(self) -
                 textLeftSizeArea) / (len(self['data']) - 1)
        posYRes = DGH.getRealTop(self) / (self['numPosSteps']
                                          if self['numPosSteps'] > 0 else max(
                                              self['data']))
        negYRes = DGH.getRealBottom(self) / (-self['numNegSteps']
                                             if self['numNegSteps'] > 0 else
                                             min(self['data']))

        # remove old content
        if self.lines is not None:
            self.lines.removeNode()

        if self.measureLines is not None:
            self.measureLines.removeNode()

        if self.centerLine is not None:
            self.centerLine.removeNode()

        for text in self.xDescriptions:
            text.removeNode()
        self.xDescriptions = []

        for text in self.points:
            text.removeNode()
        self.points = []

        # prepare the line drawings
        self.lines = LineNodePath(parent=self,
                                  thickness=3.0,
                                  colorVec=(1, 0, 0, 1))
        self.measureLines = LineNodePath(parent=self,
                                         thickness=1.0,
                                         colorVec=(0, 0, 0, 1))
        self.centerLine = LineNodePath(parent=self,
                                       thickness=2.0,
                                       colorVec=(0, 0, 0, 1))

        # draw the center line
        self.centerLine.reset()
        self.centerLine.drawLines([((diagramLeft, 0, 0), (right, 0, 0))])
        self.centerLine.create()

        self.xDescriptions.append(
            self.createcomponent('value0', (),
                                 None,
                                 DirectLabel, (self, ),
                                 text="0",
                                 text_scale=self['numtextScale'],
                                 text_align=TextNode.ARight,
                                 pos=(diagramLeft, 0, -0.01),
                                 relief=None,
                                 state='normal'))

        # calculate the positive measure lines and add the numbers
        measureLineData = []
        numSteps = (self['numPosSteps'] if self['numPosSteps'] > 0 else
                    math.floor(max(self['data']))) + 1
        for i in range(1, numSteps, self['numPosStepsStep']):
            measureLineData.append(
                ((diagramLeft, 0, i * posYRes), (right, 0, i * posYRes)))

            calcBase = 1 / DGH.getRealTop(self)
            maxData = self['numPosSteps'] if self['numPosSteps'] > 0 else max(
                self['data'])
            value = self['stepFormat'](round(i * posYRes * calcBase * maxData,
                                             self['stepAccuracy']))
            y = i * posYRes
            self.xDescriptions.append(
                self.createcomponent('value{}'.format(value), (),
                                     None,
                                     DirectLabel, (self, ),
                                     text=str(value),
                                     text_scale=self['numtextScale'],
                                     text_align=TextNode.ARight,
                                     pos=(diagramLeft, 0, y - 0.025),
                                     relief=None,
                                     state='normal'))

        # calculate the negative measure lines and add the numbers
        numSteps = (self['numNegSteps'] if self['numNegSteps'] > 0 else
                    math.floor(abs(min(self['data'])))) + 1
        for i in range(1, numSteps, self['numNegStepsStep']):
            measureLineData.append(
                ((diagramLeft, 0, -i * negYRes), (right, 0, -i * negYRes)))

            calcBase = 1 / DGH.getRealBottom(self)
            maxData = self['numPosSteps'] if self['numPosSteps'] > 0 else max(
                self['data'])
            value = self['stepFormat'](round(i * negYRes * calcBase * maxData,
                                             self['stepAccuracy']))
            y = -i * negYRes
            self.xDescriptions.append(
                self.createcomponent('value{}'.format(value), (),
                                     None,
                                     DirectLabel, (self, ),
                                     text=str(value),
                                     text_scale=self['numtextScale'],
                                     text_align=TextNode.ARight,
                                     pos=(diagramLeft, 0, y + 0.01),
                                     relief=None,
                                     state='normal'))

        # Draw the lines
        self.measureLines.reset()
        self.measureLines.drawLines(measureLineData)
        self.measureLines.create()

        lineData = []
        for i in range(1, len(self['data'])):
            yResA = posYRes if self['data'][i - 1] >= 0 else negYRes
            yResB = posYRes if self['data'][i] >= 0 else negYRes
            lineData.append((
                # Point A
                (diagramLeft + (i - 1) * xStep, 0, self['data'][i - 1] * yResA
                 ),
                # Point B
                (diagramLeft + i * xStep, 0, self['data'][i] * yResB)))

            if (self['showDataNumbers']):
                value = round(self['data'][i - 1], self['stepAccuracy'])
                self.points.append(
                    self.createcomponent('value{}'.format(value), (),
                                         None,
                                         DirectLabel, (self, ),
                                         text=str(value),
                                         text_scale=self['dataNumtextScale'],
                                         text_align=TextNode.ARight,
                                         pos=(diagramLeft + (i - 1) * xStep, 0,
                                              self['data'][i - 1] * yResA),
                                         relief=None,
                                         state='normal'))

        # Draw the lines
        self.lines.reset()
        self.lines.drawLines(lineData)
        self.lines.create()
Exemple #6
0
class DirectGrid(NodePath, DirectObject):
    def __init__(self,
                 parent,
                 gridSize=100.0,
                 gridSpacing=5.0,
                 planeColor=(0.5, 0.5, 0.5, 0.5)):
        # Initialize superclass
        NodePath.__init__(self)
        self.assign(hidden.attachNewNode('DirectGrid'))
        # Don't wireframe or light
        #useDirectRenderStyle(self)
        self.setLightOff(0)
        self.setRenderModeFilled()
        self.parent = parent

        # Load up grid parts to initialize grid object
        # Polygon used to mark grid plane
        self.gridBack = loader.loadModel('models/misc/gridBack.egg.pz')
        self.gridBack.reparentTo(self)
        self.gridBack.setColor(*planeColor)

        # Grid Lines
        self.lines = self.attachNewNode('gridLines')
        self.minorLines = LineNodePath(self.lines)
        self.minorLines.lineNode.setName('minorLines')
        self.minorLines.setColor(VBase4(0.3, 0.55, 1, 1))
        self.minorLines.setThickness(1)

        self.majorLines = LineNodePath(self.lines)
        self.majorLines.lineNode.setName('majorLines')
        self.majorLines.setColor(VBase4(0.3, 0.55, 1, 1))
        self.majorLines.setThickness(5)

        self.centerLines = LineNodePath(self.lines)
        self.centerLines.lineNode.setName('centerLines')
        self.centerLines.setColor(VBase4(1, 0, 0, 0))
        self.centerLines.setThickness(3)

        # Small marker to hilight snap-to-grid point
        self.snapMarker = loader.loadModel('models/misc/sphere.egg.pz')
        self.snapMarker.node().setName('gridSnapMarker')
        self.snapMarker.reparentTo(self)
        self.snapMarker.setColor(1, 0, 0, 1)
        self.snapMarker.setScale(0.3)
        self.snapPos = Point3(0)

        # Initialize Grid characteristics
        self.fXyzSnap = 1
        self.fHprSnap = 1
        self.gridSize = gridSize
        self.gridSpacing = gridSpacing
        self.snapAngle = 15.0
        self.enable()

    def enable(self):
        self.reparentTo(self.parent)
        self.updateGrid()
        self.fEnabled = 1

    def disable(self):
        self.reparentTo(hidden)
        self.fEnabled = 0

    def toggleGrid(self):
        if self.fEnabled:
            self.disable()
        else:
            self.enable()

    def isEnabled(self):
        return self.fEnabled

    def updateGrid(self):
        # Update grid lines based upon current grid spacing and grid size
        # First reset existing grid lines
        self.minorLines.reset()
        self.majorLines.reset()
        self.centerLines.reset()

        # Now redraw lines
        numLines = int(math.ceil(self.gridSize / self.gridSpacing))
        scaledSize = numLines * self.gridSpacing

        center = self.centerLines
        minor = self.minorLines
        major = self.majorLines
        for i in range(-numLines, numLines + 1):
            if i == 0:
                center.moveTo(i * self.gridSpacing, -scaledSize, 0)
                center.drawTo(i * self.gridSpacing, scaledSize, 0)
                center.moveTo(-scaledSize, i * self.gridSpacing, 0)
                center.drawTo(scaledSize, i * self.gridSpacing, 0)
            else:
                if (i % 5) == 0:
                    major.moveTo(i * self.gridSpacing, -scaledSize, 0)
                    major.drawTo(i * self.gridSpacing, scaledSize, 0)
                    major.moveTo(-scaledSize, i * self.gridSpacing, 0)
                    major.drawTo(scaledSize, i * self.gridSpacing, 0)
                else:
                    minor.moveTo(i * self.gridSpacing, -scaledSize, 0)
                    minor.drawTo(i * self.gridSpacing, scaledSize, 0)
                    minor.moveTo(-scaledSize, i * self.gridSpacing, 0)
                    minor.drawTo(scaledSize, i * self.gridSpacing, 0)

        center.create()
        minor.create()
        major.create()
        if (self.gridBack):
            self.gridBack.setScale(scaledSize)

    def setXyzSnap(self, fSnap):
        self.fXyzSnap = fSnap

    def getXyzSnap(self):
        return self.fXyzSnap

    def setHprSnap(self, fSnap):
        self.fHprSnap = fSnap

    def getHprSnap(self):
        return self.fHprSnap

    def computeSnapPoint(self, point):
        # Start of with current point
        self.snapPos.assign(point)
        # Snap if necessary
        if self.fXyzSnap:
            self.snapPos.set(ROUND_TO(self.snapPos[0], self.gridSpacing),
                             ROUND_TO(self.snapPos[1], self.gridSpacing),
                             ROUND_TO(self.snapPos[2], self.gridSpacing))

        # Move snap marker to this point
        self.snapMarker.setPos(self.snapPos)

        # Return the hit point
        return self.snapPos

    def computeSnapAngle(self, angle):
        return ROUND_TO(angle, self.snapAngle)

    def setSnapAngle(self, angle):
        self.snapAngle = angle

    def getSnapAngle(self):
        return self.snapAngle

    def setGridSpacing(self, spacing):
        self.gridSpacing = spacing
        self.updateGrid()

    def getGridSpacing(self):
        return self.gridSpacing

    def setGridSize(self, size):
        # Set size of grid back and redraw lines
        self.gridSize = size
        self.updateGrid()

    def getGridSize(self):
        return self.gridSize
Exemple #7
0
class Rocket (Body):

    family = "rocket"
    species = "generic"
    longdes = _("generic")
    shortdes = _("G/RCK")
    cpitdes = {}
    against = []
    mass = 150.0
    diameter = 0.140
    maxg = 20.0
    vmaxalt = 12000.0
    minspeed = 470.0
    minspeed1 = 470.0
    maxspeed = 650.0
    maxspeed1 = 850.0
    maxthracc = 300.0
    maxthracc1 = 400.0
    maxvdracc = 2.0
    maxvdracc1 = 1.0
    maxflighttime = 20.0
    minlaunchdist = 1000.0
    hitforce = 5.0
    expforce = 50.0
    seeker = "ir"
    flightmodes = ["intercept"]
    maxoffbore = radians(10.0)
    locktime = 2.0
    decoyresist = 0.7
    rcs = 0.00010
    hitboxdata = [(Point3(0.0, 0.0, 0.0), 1.0)]
    modelpath = None
    texture = None
    normalmap = None
    glowmap = "models/weapons/_glowmap.png"
    glossmap = "models/weapons/_glossmap.png"
    modelscale = 1.0
    modeloffset = Point3()
    modelrot = Vec3()
    engsoundname = None
    engvol = 0.3
    launchvol = 0.5
    expvol = 0.8

    _seekers_local = set(("ir", "tv", "intv", "arh"))
    _seekers_remote = set(("sarh", "salh", "radio", "wire"))
    _seekers_none = set((None,))
    _seekers_all = _seekers_local.union(_seekers_remote).union(_seekers_none)

    _flightmodes_all = set(("transfer", "intercept", "inertial"))

    def __init__ (self, world, name, side,
                  pos=None, hpr=None, speed=None, dropvel=None,
                  target=None, offset=None,
                  extvis=True):

        if pos is None:
            pos = Vec3()
        if hpr is None:
            hpr = Vec3()
        if speed is None:
            d1, maxspeed = self.limspeeds(pos[2])
            speed = maxspeed

        if False: # no point checking in every instance...
            if self.seeker not in Rocket._seekers_all:
                raise StandardError(
                    "Unknown seeker type '%s' for '%s'." %
                    (self.seeker, self.species))
            unknown = set(self.flightmodes).difference(Rocket._flightmodes_all)
            if unknown:
                raise StandardError(
                    "Unknown flight mode '%s' for '%s'." %
                    (unknown.pop(), self.species))

        if dropvel is not None:
            vel = hprtovec(hpr) * speed + dropvel
        else:
            vel = speed

        Body.__init__(self,
            world=world,
            family=self.family, species=self.species,
            hitforce=self.hitforce,
            modeldata=AutoProps(
                path=self.modelpath,
                texture=self.texture, normalmap=self.normalmap,
                glowmap=self.glowmap, glossmap=self.glossmap,
                scale=self.modelscale,
                offset=self.modeloffset, rot=self.modelrot),
            amblit=True, dirlit=True, pntlit=1, fogblend=True,
            ltrefl=(self.glossmap is not None),
            name=name, side=side,
            pos=pos, hpr=hpr, vel=vel)

        self.ming = -self.maxg

        if self.engsoundname:
            self.engine_sound = Sound3D(
                path=("audio/sounds/%s.ogg" % self.engsoundname),
                parent=self, limnum="hum", volume=self.engvol, loop=True)
            self.engine_sound.play()
        if 1:
            lnchsnd = Sound3D(
                "audio/sounds/%s.ogg" % "missile-launch",
                parent=self, volume=self.launchvol, fadetime=0.1)
            lnchsnd.play()

        self.target = target
        self.offset = offset
        self.must_hit_expforce = 0.0
        self.proximity_fuze_active = True
        self._last_target = target

        self.path = None
        self.pspeed = None

        self._targeted_offset = (self.offset or
                                 (self.target and self.target.center) or
                                 Point3())
        self._effective_offset = self._targeted_offset

        self._actdist = min(0.5 * self.minlaunchdist, 1000.0)
        self._active = False
        self._armdist = self.minlaunchdist
        self._armed = False

        self._state_info_text = None
        self._wait_time_state_info = 0.0

        self._prev_path = None
        self._path_pos = 0.0

        self.exhaust_trails = []

        if side == "bstar":
            trcol = rgba(127, 0, 0, 1)
        elif side == "nato":
            trcol = rgba(0, 0, 127, 1)
        else:
            trcol = rgba(0, 127, 0, 1)
        self._trace = None
        if world.show_traces:
            self._trace = LineNodePath(parent=self.world.node,
                                       thickness=1.0, colorVec=trcol)
            self._trace_segs = []
            self._trace_lens = []
            self._trace_len = 0.0
            self._trace_maxlen = 5000.0
            self._trace_prevpos = pos
            self._trace_frameskip = 5
            self._trace_accuframe = 0

        self._flightdist = 0.0
        self._flighttime = 0.0

        self._updperiod_decoy = 0.053
        self._updwait_decoy = 0.0
        self._dtime_decoy_process = 0.0
        self._eliminated_decoys = set()
        self._tracked_decoy = None

        self._no_target_selfdest_time = 1.0
        self._no_target_time = 0.0

        test_expforce = max(self.expforce * 0.9, self.expforce - 1.0)
        self._prox_dist = explosion_reach(test_expforce)
        self.proximity_fuzed = False # debug
        self.target_hit = False # debug

        if extvis:
            bx, by, bz = self.bbox
            bbox = Vec3(bx, by * 500.0, bz)
            EnhancedVisual(parent=self, bbox=bbox)

        self._fudge_player_manoeuver = True
        if self._fudge_player_manoeuver:
            self._plmanv_done_1 = False
            self._plmanv_done_2 = False

        base.taskMgr.add(self._loop, "rocket-loop-%s" % self.name)


    def _loop (self, task):

        if not self.alive:
            return task.done
        if self.target and not self.target.alive:
            self.target = None

        # Keep track of last known and alive target.
        # Needed e.g. to apply force explosion damage.
        if self.target and self.target.alive:
            self._last_target = self.target
        elif self._last_target and not self._last_target.alive:
            self._last_target = None

        dt = self.world.dt
        pos = self.pos()
        vel = self.vel()

        if self.world.below_surface(pos):
            posg = self.world.intersect_surface(pos - vel * dt, pos)
            self.explode(pos=posg)
            return task.done

        if self._flighttime >= self.maxflighttime:
            self.explode()
            return task.done

        if not self._active and (self._flightdist >= self._actdist or
                                 self.must_hit_expforce > 0.0):
            self._active = True
            # Initial autopilot state.
            self._ap_currctl = None
            self._ap_pause = 0.0

        if not self._armed and self._flightdist >= self._armdist:
            self.set_hitboxes(hitboxdata=self.hitboxdata)
            self._armed = True

        # Update offset for decoys.
        if self.target:
            self._updwait_decoy -= dt
            self._dtime_decoy_process += dt
            if self._updwait_decoy <= 0.0:
                self._updwait_decoy += self._updperiod_decoy
                ret = self._process_decoys(self.target, self._targeted_offset,
                                           self._dtime_decoy_process)
                self._effective_offset = ret
                self._dtime_decoy_process = 0.0

        # Activate proximity fuze if near miss.
        if (self._armed and self.proximity_fuze_active and self.target and
            not self.world.in_collision(self)):
            tdir = self.target.pos(refbody=self, offset=self._targeted_offset)
            # ...not self._effective_offset.
            tdist = tdir.length()
            #print_each(1350, 1.0, "--rck-prox-check tdist=%.0f[m]" % tdist)
            if tdist < self._prox_dist or self.must_hit_expforce > 0.0:
                tdir.normalize()
                offbore = acos(clamp(tdir[1], -1.0, 1.0))
                if offbore > self.maxoffbore:
                    #print "--rck-proxfuze", tdist, degrees(offbore)
                    self.explode()
                    self.proximity_fuzed = True # debug
                    return task.done

        # Apply autopilot.
        if self._active:
            if not self.target:
                # TODO: Look for another target?
                if self.seeker not in Rocket._seekers_none:
                    self._no_target_time += dt
                    if self._no_target_time >= self._no_target_selfdest_time:
                        self.explode()
                        return task.done
            else:
                self._no_target_time = 0.0
            self._ap_pause -= dt
            if self._ap_pause <= 0.0:
                self._ap_pause = self._ap_input(dt)
                if self._ap_pause is None:
                    self.target = None
                    self._ap_pause = 2.0

        #for trail in self.exhaust_trails:
            #pass

        # Update trace (debugging).
        if self._trace is not None:
            self._trace_accuframe += 1
            if self._trace_accuframe >= self._trace_frameskip:
                self._trace_accuframe = 0
                while self._trace_len >= self._trace_maxlen and self._trace_segs:
                    tseg = self._trace_segs.pop(0)
                    tlen = self._trace_lens.pop(0)
                    self._trace_len -= tlen
                self._trace_segs.append((self._trace_prevpos, pos))
                self._trace_lens.append((pos - self._trace_prevpos).length())
                self._trace_len += self._trace_lens[-1]
                self._trace.reset()
                self._trace.drawLines(self._trace_segs)
                #self._trace.drawTo(pos)
                self._trace.create()
                self._trace_prevpos = pos

        return task.cont


    def destroy (self):

        if not self.alive:
            return

        if self._trace is not None:
            self._trace.removeNode()
        Body.destroy(self)


    def collide (self, obody, chbx, cpos):

        inert = Body.collide(self, obody, chbx, cpos, silent=True)
        if inert:
            return True

        self.target_hit = (obody is self.target) # debug

        self.explode()

        return False


    def explode (self, pos=None):

        if not self.alive:
            return

        if pos is None:
            pos = self.pos()
        if self.world.otr_altitude(pos) < 20.0:
            debrispitch = (10, 80)
        else:
            debrispitch = (-90, 90)
        exp = PolyExplosion(
            world=self.world, pos=pos,
            sizefac=1.4, timefac=0.4, amplfac=0.8,
            smgray=pycv(py=(115, 150), c=(220, 255)), smred=0, firepeak=(0.5, 0.8),
            debrispart=(3, 6),
            debrispitch=debrispitch)
        snd = Sound3D(
            "audio/sounds/%s.ogg" % "explosion-missile",
            parent=exp, volume=self.expvol, fadetime=0.1)
        snd.play()

        self.shotdown = True
        self.destroy()

        touch = []
        if self.must_hit_expforce > 0.0 and self._last_target:
            touch = [(self._last_target, self.must_hit_expforce)]
        self.world.explosion_damage(force=self.expforce, ref=self, touch=touch)


    def move (self, dt):
        # Base override.
        # Called by world at end of frame.

        if dt == 0.0:
            return

        pos = vtod(self.pos())
        alt = pos[2]
        vel = vtod(self.vel())
        vdir = unitv(vel)
        speed = vel.length()
        quat = qtod(self.quat())
        udir = quat.getUp()
        fdir = quat.getForward()
        rdir = quat.getRight()
        angvel = vtod(self.angvel())

        if self._prev_path is not self.path: # must come before next check
            self._prev_path = self.path
            self._path_pos = 0.0
        if self.path is None or self.path.length() < self._path_pos:
            tdir = vdir
            ndir = udir
            if self.path is not None:
                tdir = self.path.tangent(self.path.length())
                ndir = self.path.normal(self.path.length())
            self.path = Segment(Vec3D(), tdir * 1e5, ndir)
            self._prev_path = self.path
            self._path_pos = 0.0

        # ====================
        # Translation.

        minspeed, maxspeed = self.limspeeds(alt)
        if self.pspeed is None:
            self.pspeed = maxspeed
        self.pspeed = clamp(self.pspeed, minspeed, maxspeed)
        minacc, maxacc = self.limaccs(alt=alt, speed=speed)
        dspeed = self.pspeed - speed
        if dspeed >= 0.0:
            tacc = min(dspeed * 1.0, maxacc)
        else:
            tacc = max(dspeed * 1.0, minacc)

        s = self._path_pos
        dp = self.path.point(s)
        t = self.path.tangent(s)
        tvel = vel.dot(t)
        s1 = s + tvel * dt + tacc * (0.5 * dt**2)
        dp1 = self.path.point(s1)
        tvel1 = tvel + tacc * dt
        t1 = self.path.tangent(s1)
        vel1 = t1 * tvel1
        dpos = dp1 - dp
        #acc = (dpos - vel * dt) / (0.5 * dt**2)
        n1 = self.path.normal(s1)
        r1 = self.path.radius(s1)
        acc = t1 * tacc + n1 * (tvel1**2 / r1)
        self._path_pos = s1

        self.node.setPos(vtof(pos + dpos))
        self._prev_vel = Vec3(self._vel) # needed in base class
        self._vel = vtof(vel1) # needed in base class
        self._acc = vtof(acc) # needed in base class

        # ====================
        # Rotation.

        fdir1 = t1
        paxis = fdir.cross(fdir1)
        if paxis.length() > 1e-5:
            paxis.normalize()
            dspitch = fdir.signedAngleRad(fdir1, paxis)
        else:
            paxis = rdir
            dspitch = 0.0
        pdang = dspitch
        pdquat = QuatD()
        pdquat.setFromAxisAngleRad(pdang, paxis)

        dquat = pdquat
        quat1 = quat * dquat

        angvel1 = (paxis * pdang) / dt
        angacc = (angvel1 - angvel) / dt

        self.node.setQuat(qtof(quat1))
        self._angvel = vtof(angvel1) # needed in base class
        self._angacc = vtof(angacc) # needed in base class

        self._flighttime += dt
        self._flightdist += dpos.length()

        # print_each(105, 0.25, "--rck1", pos, speed, self._flighttime)


    def limspeeds (self, alt=None):

        if alt is None:
            alt = self.pos()[2]

        return self.limspeeds_st(self, alt)


    @staticmethod
    def limspeeds_st (clss, alt):

        minspeed0, minspeed0b = clss.minspeed, clss.minspeed1
        maxspeed0, maxspeed0b = clss.maxspeed, clss.maxspeed1

        alt0b = clss.vmaxalt
        if alt < alt0b:
            ifac = alt / alt0b
            minspeed = minspeed0 + (minspeed0b - minspeed0) * ifac
            maxspeed = maxspeed0 + (maxspeed0b - maxspeed0) * ifac
        else:
            minspeed = minspeed0b
            maxspeed = maxspeed0b

        return minspeed, maxspeed


    def limaccs (self, alt=None, speed=None):

        if alt is None:
            alt = self.pos()[2]
        if speed is None:
            speed = self.speed()

        return self.limaccs_st(self, alt, speed)


    @staticmethod
    def limaccs_st (clss, alt, speed):

        maxthracc0, maxthracc0b = clss.maxthracc, clss.maxthracc1
        maxvdracc0, maxvdracc0b = clss.maxvdracc, clss.maxvdracc1

        # Altitude influence.
        alt0b = clss.vmaxalt
        if alt < alt0b:
            afac = alt / alt0b
            maxthracc = maxthracc0 + afac * (maxthracc0b - maxthracc0)
            maxvdracc = maxvdracc0 + afac * (maxvdracc0b - maxvdracc0)
        else:
            maxthracc = maxthracc0b
            maxvdracc = maxvdracc0b

        # Speed influence.
        minspeed, maxspeed = clss.limspeeds_st(clss, alt)
        sfac = speed / maxspeed
        minacc0 = maxvdracc * -sfac
        maxacc0 = maxthracc * (1.0 - sfac)
        minacc = minacc0
        maxacc = maxacc0

        return minacc, maxacc


    def _ap_input (self, dt):

        w = self.world
        t = self.target

        #print "========== rck-ap-start (world-time=%.2f)" % (w.time)

        # Choose initial control and flight mode.
        if self._ap_currctl is None:
            if self.seeker in Rocket._seekers_local:
                self._ap_currctl = "local"
            elif self.seeker in Rocket._seekers_remote:
                self._ap_currctl = "remote"
            else: # self.seeker in Rocket._seekers_none
                self._ap_currctl = "continue"
            if "transfer" in self.flightmodes:
                self._ap_currflt = "transfer"
            elif "intercept" in self.flightmodes:
                self._ap_currflt = "intercept"
            else: # "inertial" in self.flightmodes
                self._ap_currflt = "inertial"

        pos = ptod(self.pos())
        alt = pos[2]
        vel = vtod(self.vel())
        vdir = unitv(vel)
        speed = vel.length()
        if t and t.alive:
            tpos = ptod(t.pos(offset=self._effective_offset))
            tvel = vtod(t.vel())
            tspeed = tvel.length()
            tacc = vtod(t.acc())
            tabsacc = tacc.length()
        ppitch = self.ppitch()

        minspeed, maxspeed = self.limspeeds(alt)
        maxlfac = self.maxg
        minlfac = self.ming

        if t and t.alive:
            dpos = tpos - pos
            tdist = dpos.length()
            tdir = unitv(dpos)

        # Check if still tracking and return if not.
        havectl = False
        while not havectl: # control may switch
            if t and t.alive:
                if self._ap_currctl == "local":
                    # Check if the target is still within seeker FOV.
                    cosoffbore = tdir.dot(vdir)
                    tracking = (cosoffbore > cos(self.maxoffbore) or
                                self.must_hit_expforce > 0.0)
                    if tracking:
                        havectl = True
                    else:
                        if self.seeker in Rocket._seekers_remote:
                            self._ap_currctl = "remote"
                            # FIXME: What if no transfer mode?
                            self._ap_currflt = "transfer"
                        else:
                            havectl = True
                elif self._ap_currctl == "remote":
                    # TODO: Check if parent still tracks the target.
                    tracking = True
                    havectl = True
                elif self._ap_currctl == "continue":
                    tracking = False
                    havectl = True
            else:
                self._ap_currctl == "continue"
                tracking = False
                havectl = True

        if self._fudge_player_manoeuver:
            # Check special player manoeuvring to throw off the missile.
            player = self.world.player
            if tracking and player and t is player.ac:
                outer_time = 4.0 # as for outer pip on missile tracker
                inner_time = 2.0 # as for inner pip on missile tracker
                rel_reset_time = 1.2
                lim_pitch_ang = radians(-20)
                rel_max_load = 0.8
                lim_aspect_ang = radians(45)
                assert inner_time < outer_time
                assert rel_reset_time > 1.0
                assert pi * -0.5 < lim_pitch_ang < 0.0
                assert 0.0 < rel_max_load < 1.0
                assert 0.0 < lim_aspect_ang < pi * 0.5
                intc_time = tdist / speed
                pdq = player.ac.dynstate
                if intc_time > outer_time * rel_reset_time:
                    self._plmanv_done_1 = False
                    self._plmanv_done_2 = False
                if not self._plmanv_done_1:
                    if intc_time < outer_time:
                        udir = vtof(pdq.xit)
                        pitch_ang = 0.5 * pi - acos(clamp(udir[2], -1.0, 1.0))
                        if pitch_ang < lim_pitch_ang:
                            self._plmanv_done_1 = True
                if not self._plmanv_done_2:
                    if intc_time < inner_time:
                        ndir = pdq.xin
                        aspect_ang = acos(clamp(ndir.dot(-tdir), -1.0, 1.0))
                        if aspect_ang < lim_aspect_ang:
                            nmaxv = pdq.nmaxvab if pdq.hasab else pdq.nmaxv
                            nmaxc = min(pdq.nmax, nmaxv)
                            if pdq.n > nmaxc * rel_max_load:
                                self._plmanv_done_2 = True
                if self._plmanv_done_1 and self._plmanv_done_2:
                    tracking = False
                    dbgval(1, "missile-avoid",
                           (w.time, "%.2f", "time", "s"),
                           (self.name, "%s", "name"))

        if tracking:
            # Compute location of the target at interception,
            # assuming that its acceleration is constant,
            # and that parent points in the exact direction.
            # Compute with higher precision when near enough.
            sfvel = Vec3D()
            sdvelp = speed
            sfacc = Vec3D() # or self.acc()?
            sdaccp = 0.0
            ret = intercept_time(tpos, tvel, tacc,
                                 pos, sfvel, sdvelp, sfacc, sdaccp,
                                 finetime=2.0, epstime=(dt * 0.5), maxiter=5)
            if not ret:
                ret = 0.0, tpos, vdir
            inttime, tpos1, idir = ret

            # Modify intercept according to current state and mode.
            havemod = False
            while not havemod: # mode may switch
                if self._ap_currflt == "intercept":
                    havemod = True
                    adt = dt
                    # Modify intercept to keep sufficiently within boresight.
                    if self._ap_currctl == "local":
                        safeoffbore = self.maxoffbore * 0.8
                        offbore1 = acos(clamp(idir.dot(tdir), -1.0, 1.0))
                        if offbore1 > safeoffbore:
                            a1u = unitv(tpos1 - tpos)
                            ang1 = acos(clamp(a1u.dot(-tdir), -1.0, 1.0))
                            ang2 = pi - ang1 - safeoffbore
                            tdist1c = tdist * (sin(ang1) / sin(ang2))
                            anu = unitv(tdir.cross(idir))
                            q = QuatD()
                            q.setFromAxisAngleRad(safeoffbore, anu)
                            idirc = Vec3D(q.xform(tdir))
                            tpos1c = pos + idirc * tdist1c
                            tpos1 = tpos1c
                elif self._ap_currflt == "transfer":
                    tointtime = 15.0 #!!!
                    if inttime < tointtime:
                        offbore1 = acos(clamp(unitv(tpos - pos).dot(vdir), -1.0, 1.0))
                        safeoffbore = self.maxoffbore * 0.8
                        if offbore1 < safeoffbore:
                            # FIXME: What if no intercept mode?
                            self._ap_currflt = "intercept"
                            if self.seeker in Rocket._seekers_local:
                                self._ap_currctl = "local"
                    if self._ap_currflt == "transfer": # mode not switched
                        havemod = True
                        maxadt = 1.0
                        if inttime < tointtime:
                            adt = clamp((tointtime - inttime) * 0.1, dt, maxadt)
                            # Direct towards intercept.
                            tpos1 = tpos
                        else:
                            # Climb to best altitude, in direction of intercept.
                            adt = maxadt
                            dpos1 = tpos1 - pos
                            tdir1 = unitv(dpos1)
                            #maxlfac = max(maxlfac * 0.2, min(maxlfac, 6.0))
                            maxlfac = min(maxlfac, 12.0)
                            mintrad = speed**2 / (maxlfac * w.absgravacc)
                            tralt = max(self.vmaxalt, tpos[2])
                            daltopt = tralt - alt
                            cpitch = (1.0 - (alt / tralt)**2) * (0.5 * pi)
                            if self._ap_currctl == "local":
                                # If local control in transfer, must keep in bore.
                                safeoffbore = self.maxoffbore * 0.8
                                tpitch = atan(tdir1[2] / tdir1.getXy().length())
                                cpitch = clamp(cpitch,
                                               tpitch - safeoffbore,
                                               tpitch + safeoffbore)
                            tdir1xy = unitv(Vec3D(tdir1[0], tdir1[1], 0.0))
                            dpos1mxy = tdir1xy * (daltopt / tan(cpitch))
                            dpos1m = dpos1mxy + Vec3D(0.0, 0.0, daltopt)
                            tpos1 = pos + unitv(dpos1m) * (mintrad * 10.0)
                elif self._ap_currflt == "inertial":
                    pass
            #print("--rck-ap-state  ctl=%s  flt=%s  tdist=%.0f[m]  alt=%.0f[m]  ppitch=%.1f[deg]  adt=%.3f[s]" %
                #(self._ap_currctl, self._ap_currflt, tdist, alt, degrees(ppitch), adt))
        else:
            tpos1 = pos + vdir * (speed * 1.0)
            adt = None

        # Input path.
        dpos1 = tpos1 - pos
        tdist1 = dpos1.length()
        tdir1 = unitv(dpos1)
        ndir = vdir.cross(tdir1).cross(vdir)
        if ndir.length() > 1e-5:
            ndir.normalize()
            dpos1n = dpos1.dot(ndir) or 1e-10
            maxturntime = 5.0 #!!!
            turndist = min(tdist1, maxspeed * maxturntime)
            trad = turndist**2 / (2 * dpos1n)
            lfac = (speed**2 / trad) / w.absgravacc
            if lfac > maxlfac:
                lfac = maxlfac
                trad = speed**2 / (lfac * w.absgravacc)
        else:
            ndir = vtod(self.quat().getUp())
            trad = 1e6
        if trad < 1e6:
            tang = 2 * asin(clamp(dpos1n / tdist1, -1.0, 1.0))
            path = Arc(trad, tang, Vec3D(), vdir, ndir)
            #print "--hmap1  tdist=%.1f[m]  speed=%.1f[m/s]  trad=%.1f[m]  tang=%.1f[deg]  lfac=%.1f" % (tdist, speed, trad, degrees(tang), lfac)
        else:
            path = Segment(Vec3D(), vdir * 1e5, ndir)
            # print "--hmap2"
        self.path = path

        # # Input path.
        # vdir = unitv(vel)
        # dpos = tpos - pos
        # tdist = dpos.length()
        # tdir = unitv(dpos)
        # ndir = unitv(vdir.cross(tdir).cross(vdir))
        # dposn = dpos.dot(ndir) or 1e-5
        # dpost = dpos.dot(vdir) or 1e-5
        # maxtrad = tdist**2 / (2 * dposn)
        # mrlfac = (speed**2 / maxtrad) / w.absgravacc
        # if mrlfac < maxlfac:
            # lfac1 = clamp((maxlfac * 1e3) / tdist, 2.0, maxlfac)
            # trad = speed**2 / (lfac1 * w.absgravacc)
            # tang = atan(dposn / dpost)
            # tang_p = 2 * tang
            # niter = 0
            # maxiter = 10
            # while abs(tang_p - tang) > 1e-4 and niter < maxiter:
                # tang_p = tang
                # k1 = tan(tang)
                # k2 = sqrt(1.0 + k1**2)
                # tang = atan(((dposn - trad) * k2 + trad) / (dpost * k2 - trad * k1))
                # niter += 1
            # path = Arc(trad, tang, Vec3D(), vdir, ndir)
            # print "--hmap1  maxiter=%d  iter=%d  tdist=%.1f  speed=%.1f  trad=%.1f  tang=%.1f" % (maxiter, niter, tdist, speed, trad, degrees(tang))
        # else:
            # path = Segment(Vec3D(), vdir * 1e5, ndir)
            # print "--hmap2"
        # self.path = path

        # Input speed.
        self.pspeed = maxspeed

        #print "========== rck-ap-end"
        return adt


    @staticmethod
    def check_launch_free (launcher=None):

        return True


    def exec_post_launch (self, launcher=None):

        pass


    def _process_decoys (self, target, toffset, dtime):


        target_dir = unitv(target.pos(refbody=self))
        target_offbore = acos(clamp(target_dir[1], -1.0, 1.0))
        if target_offbore < self.maxoffbore:
            target_fwd = target.quat(refbody=self).getForward()
            target_aspect = acos(clamp(target_dir.dot(target_fwd), -1.0, 1.0))
            target_weight = intl01v((target_aspect / pi)**0.5, 1.0, 0.2)
            resist_mod = target_weight**((1.0 - self.decoyresist)**0.5)
            offset = toffset
        else:
            target_weight = 0.0
            resist_mod = 1.0
            offset = self._effective_offset # last offset

        while True:
            if not self._tracked_decoy:
                num_tested = 0
                for decoy in target.decoys:
                    if decoy.alive and decoy not in self._eliminated_decoys:
                        tracked = False
                        decoy_offbore = self.offbore(decoy)
                        if decoy_offbore < self.maxoffbore:
                            num_tested += 1
                            if randunit() > self.decoyresist * resist_mod:
                                tracked = True
                        if tracked:
                            self._tracked_decoy = decoy
                            break
                        else:
                            self._eliminated_decoys.add(decoy)
            else:
                num_tested = 1

            decoy_reloffb = 0.0
            decoy_effect = 0.0
            if self._tracked_decoy:
                decoy = self._tracked_decoy
                tracked = False
                if decoy.alive:
                    decoy_offbore = self.offbore(decoy)
                    if decoy_offbore < self.maxoffbore:
                        if target_weight and decoy_offbore > target_offbore:
                            decoy_reloffb = (target_offbore / decoy_offbore)**0.5
                        else:
                            decoy_reloffb = 1.0
                        decoy_effect = (1.0 - decoy.decay()) * decoy_reloffb
                        if decoy_effect > self.decoyresist * resist_mod:
                            offset = decoy.pos(refbody=target)
                            tracked = True
                if not tracked:
                    self._tracked_decoy = None
                    self._eliminated_decoys.add(decoy)

            if self._tracked_decoy or num_tested == 0:
                break

        #vf = lambda v, d=3: "(%s)" % ", ".join(("%% .%df" % d) % e for e in v)
        #num_seen = len(self._eliminated_decoys) + int(bool(self._tracked_decoy))
        #num_tracked = int(bool(self._tracked_decoy))
        #target_dist = self.pos(refbody=target, offset=toffset).length()
        #doffset = offset - toffset
        #print ("--procdec  num_seen=%d  target_weight=%.2f  "
               #"decoy_reloffb=%.2f  decoy_effect=%.2f  "
               #"target_dist=%.0f  doffset=%s"
               #% (num_seen, target_weight, decoy_reloffb, decoy_effect,
                  #target_dist, vf(doffset)))

        return offset


    @classmethod
    def launch_limits (cls, attacker, target, offset=None):

        weapon = cls

        apos = attacker.pos()
        pdir = attacker.quat().getForward()
        tpos = target.pos(offset=offset)
        tvel = target.vel()
        tspd = tvel.length()

        # Maximum range for non-manouevring target.
        malt = 0.5 * (apos[2] + tpos[2])
        spds = weapon.limspeeds_st(weapon, malt)
        accs = weapon.limaccs_st(weapon, malt, 0.0)
        fltime = weapon.maxflighttime - 0.5 * (spds[1] / accs[1])
        tvel1 = tvel
        fltime1 = fltime * 0.90 # safety
        rmax = max_intercept_range(tpos, tvel1, apos, spds[1], fltime1)
        # - correct once for bore-keeping (overcorrection)
        tpos1 = tpos + tvel1 * fltime1
        tdir1 = unitv(tpos1 - apos)
        offbore1 = acos(clamp(tdir1.dot(pdir), -1.0, 1.0))
        if offbore1 > weapon.maxoffbore:
            tdist1 = (tpos1 - apos).length()
            dbore1 = offbore1 - weapon.maxoffbore
            tarc1 = (tdist1 / sin(dbore1)) * dbore1
            fltime2 = fltime1 * (tdist1 / tarc1)
            rmax = max_intercept_range(tpos, tvel1, apos, spds[1], fltime2)

        # Maximum range for manouevring target.
        if hasattr(target, "mass"):
            #tminmass = getattr(target, "minmass", target.mass)
            ##tspds = target.limspeeds(mass=tminmass, alt=tpos[2], withab=True)
            #taccs = target.limaccs(mass=tminmass, alt=tpos[2], speed=tspd,
                                   #climbrate=0.0, turnrate=0.0,
                                   #ppitch=radians(-30.0), withab=True)
            tdpos = tpos - apos
            tdist = tdpos.length()
            #fltime1 = min(fltime, tdist / spds[1])
            tspd1 = tspd #+ 0.5 * fltime1 * taccs[1]
            tvel1 = unitv(tdpos) * tspd1
            fltime1 = fltime * 0.75 # safety inc. manoeuvring
            rman = max_intercept_range(tpos, tvel1, apos, spds[1], fltime1)
            # - correct once for bore-keeping (overcorrection)
            tpos1 = tpos + tvel1 * fltime1
            tdir1 = unitv(tpos1 - apos)
            offbore1 = acos(clamp(tdir1.dot(pdir), -1.0, 1.0))
            if offbore1 > weapon.maxoffbore:
                tdist1 = (tpos1 - apos).length()
                dbore1 = offbore1 - weapon.maxoffbore
                tarc1 = (tdist1 / sin(dbore1)) * dbore1
                fltime2 = fltime1 * (tdist1 / tarc1)
                rman1 = max_intercept_range(tpos, tvel1, apos, spds[1], fltime2)
                rman = rman1
        else:
            rman = rmax

        # Minimum range.
        rmin = weapon.minlaunchdist

        return rmin, rman, rmax
Exemple #8
0
class main(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        # Disable the default camera movements
        self.disableMouse()

        #
        # VIEW SETTINGS
        #
        self.win.setClearColor((0.16, 0.16, 0.16, 1))
        render.setAntialias(AntialiasAttrib.MAuto)
        render2d.setAntialias(AntialiasAttrib.MAuto)

        #
        # NODE VIEW
        #
        self.viewNP = aspect2d.attachNewNode("viewNP")
        self.viewNP.setScale(0.5)

        #
        # NODE MANAGER
        #
        self.nodeMgr = NodeManager(self.viewNP)

        #
        # NODE RELATED EVENTS
        #
        # Add nodes
        self.accept("addNode", self.nodeMgr.addNode)
        # Remove nodes
        self.accept("removeNode", self.nodeMgr.removeNode)
        self.accept("x", self.nodeMgr.removeNode)
        self.accept("delete", self.nodeMgr.removeNode)
        # Selecting
        self.accept("selectNode", self.nodeMgr.selectNode)
        # Deselecting
        self.accept("mouse3", self.nodeMgr.deselectAll)
        # Node Drag and Drop
        self.accept("dragNodeStart", self.setDraggedNode)
        self.accept("dragNodeMove", self.updateNodeMove)
        self.accept("dragNodeStop", self.updateNodeStop)
        # Duplicate/Copy nodes
        self.accept("shift-d", self.nodeMgr.copyNodes)
        self.accept("copyNodes", self.nodeMgr.copyNodes)
        # Refresh node logics
        self.accept("ctlr-r", self.nodeMgr.updateAllLeaveNodes)
        self.accept("refreshNodes", self.nodeMgr.updateAllLeaveNodes)

        #
        # SOCKET RELATED EVENTS
        #
        self.accept("updateConnectedNodes", self.nodeMgr.updateConnectedNodes)
        # Socket connection with drag and drop
        self.accept("startPlug", self.nodeMgr.setStartPlug)
        self.accept("endPlug", self.nodeMgr.setEndPlug)
        self.accept("connectPlugs", self.nodeMgr.connectPlugs)
        self.accept("cancelPlug", self.nodeMgr.cancelPlug)
        # Draw line while connecting sockets
        self.accept("startLineDrawing", self.startLineDrawing)
        self.accept("stopLineDrawing", self.stopLineDrawing)

        #
        # PROJECT MANAGEMENT
        #
        self.accept("new", self.newProject)
        self.accept("save", self.saveProject)
        self.accept("load", self.loadProject)
        self.accept("quit", exit)

        #
        # EDITOR VIEW
        #
        # Zooming
        self.accept("zoom", self.zoom)
        self.accept("zoom_reset", self.zoomReset)
        self.accept("wheel_up", self.zoom, [True])
        self.accept("wheel_down", self.zoom, [False])

        # Drag view
        self.mouseSpeed = 1
        self.mousePos = None
        self.startCameraMovement = False
        self.accept("mouse2", self.setMoveCamera, [True])
        self.accept("mouse2-up", self.setMoveCamera, [False])

        # Box select
        # accept the 1st mouse button events to start and stop the draw
        self.accept("mouse1", self.startBoxDraw)
        self.accept("mouse1-up", self.stopBoxDraw)
        # variables to store the start and current pos of the mousepointer
        self.startPos = LPoint2f(0, 0)
        self.lastPos = LPoint2f(0, 0)
        # variables for the to be drawn box
        self.boxCardMaker = CardMaker("SelectionBox")
        self.boxCardMaker.setColor(1, 1, 1, 0.25)
        self.box = None

        #
        # WINDOW RELATED EVENTS
        #
        self.screenSize = base.getSize()
        self.accept("window-event", self.windowEventHandler)

        #
        # MENU BAR
        #
        self.menuBar = MenuBar()

        #
        # TASKS
        #
        # Task for handling dragging of the camera/view
        self.taskMgr.add(self.updateCam, "task_camActualisation", priority=-4)

    # ------------------------------------------------------------------
    # PROJECT FUNCTIONS
    # ------------------------------------------------------------------
    def newProject(self):
        self.nodeMgr.cleanup()

    def saveProject(self):
        Save(self.nodeList, self.connections)

    def loadProject(self):
        self.nodeMgr.cleanup()
        Load(self.nodeMgr)

    # ------------------------------------------------------------------
    # CAMERA SPECIFIC FUNCTIONS
    # ------------------------------------------------------------------
    def setMoveCamera(self, moveCamera):
        """Start dragging around the editor area/camera"""
        # store the mouse position if weh have a mouse
        if base.mouseWatcherNode.hasMouse():
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
            self.mousePos = Point2(x, y)
        # set the variable according to if we want to move the camera or not
        self.startCameraMovement = moveCamera

    def updateCam(self, task):
        """Task that will move the editor area/camera around according
        to mouse movements"""
        # variables to store the mouses current x and y position
        x = 0.0
        y = 0.0
        if base.mouseWatcherNode.hasMouse():
            # get the mouse position
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
        if base.mouseWatcherNode.hasMouse() \
        and self.mousePos is not None \
        and self.startCameraMovement:
            # Move the viewer node aspect independent
            wp = self.win.getProperties()
            aspX = 1.0
            aspY = 1.0
            wpXSize = wp.getXSize()
            wpYSize = wp.getYSize()
            if wpXSize > wpYSize:
                aspX = wpXSize / float(wpYSize)
            else:
                aspY = wpYSize / float(wpXSize)
            mouseMoveX = (self.mousePos.getX() - x) / self.viewNP.getScale(
            ).getX() * self.mouseSpeed * aspX
            mouseMoveY = (self.mousePos.getY() - y) / self.viewNP.getScale(
            ).getZ() * self.mouseSpeed * aspY
            self.mousePos = Point2(x, y)

            self.viewNP.setX(self.viewNP, -mouseMoveX)
            self.viewNP.setZ(self.viewNP, -mouseMoveY)

            self.nodeMgr.updateConnections()

        # continue the task until it got manually stopped
        return task.cont

    def zoom(self, zoomIn):
        """Zoom the editor in or out dependent on the value in zoomIn"""
        zoomFactor = 0.05
        maxZoomIn = 2
        maxZoomOut = 0.1
        if zoomIn:
            s = self.viewNP.getScale()
            if s.getX() - zoomFactor < maxZoomIn and s.getY(
            ) - zoomFactor < maxZoomIn and s.getZ() - zoomFactor < maxZoomIn:
                self.viewNP.setScale(s.getX() + zoomFactor,
                                     s.getY() + zoomFactor,
                                     s.getZ() + zoomFactor)
        else:
            s = self.viewNP.getScale()
            if s.getX() - zoomFactor > maxZoomOut and s.getY(
            ) - zoomFactor > maxZoomOut and s.getZ() - zoomFactor > maxZoomOut:
                self.viewNP.setScale(s.getX() - zoomFactor,
                                     s.getY() - zoomFactor,
                                     s.getZ() - zoomFactor)
        self.nodeMgr.updateConnections()

    def zoomReset(self):
        """Set the zoom level back to the default"""
        self.viewNP.setScale(0.5)
        self.nodeMgr.updateConnections()

    # ------------------------------------------------------------------
    # DRAG LINE
    # ------------------------------------------------------------------
    def startLineDrawing(self, startPos):
        """Start a task that will draw a line from the given start
        position to the cursor"""
        self.line = LineNodePath(render2d,
                                 thickness=2,
                                 colorVec=(0.8, 0.8, 0.8, 1))
        self.line.moveTo(startPos)
        t = self.taskMgr.add(self.drawLineTask, "drawLineTask")
        t.startPos = startPos

    def drawLineTask(self, task):
        """Draws a line from a given start position to the cursor"""
        mwn = base.mouseWatcherNode
        if mwn.hasMouse():
            pos = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])

            self.line.reset()
            self.line.moveTo(task.startPos)
            self.line.drawTo(pos)
            self.line.create()
        return task.cont

    def stopLineDrawing(self):
        """Stop the task that draws a line to the cursor"""
        taskMgr.remove("drawLineTask")
        if self.line is not None:
            self.line.reset()
            self.line = None

    # ------------------------------------------------------------------
    # EDITOR NODE DRAGGING UPDATE
    # ------------------------------------------------------------------
    def setDraggedNode(self, node):
        """This will set the node that is currently dragged around
        as well as update other selected nodes which will be moved
        in addition to the main dragged node"""
        self.draggedNode = node
        self.tempNodePositions = {}
        for node in self.nodeMgr.selectedNodes:
            self.tempNodePositions[node] = node.frame.getPos(render2d)

    def updateNodeMove(self, mouseA, mouseB):
        """Will be called as long as a node is beeing dragged around"""
        for node in self.nodeMgr.selectedNodes:
            if node is not self.draggedNode and node in self.tempNodePositions.keys(
            ):
                editVec = Vec3(self.tempNodePositions[node] - mouseA)
                newPos = mouseB + editVec
                node.frame.setPos(render2d, newPos)
        self.nodeMgr.updateConnections()

    def updateNodeStop(self, node=None):
        """Will be called when a node dragging stopped"""
        self.draggedNode = None
        self.tempNodePositions = {}
        self.nodeMgr.updateConnections()

    # ------------------------------------------------------------------
    # BASIC WINDOW HANDLING
    # ------------------------------------------------------------------
    def windowEventHandler(self, window=None):
        """Custom handler for window events.
        We mostly use this for resizing."""

        # call showBase windowEvent which would otherwise get overridden and breaking the app
        self.windowEvent(window)

        if window != self.win:
            # This event isn't about our window.
            return

        if window is not None:  # window is none if panda3d is not started
            if self.screenSize == base.getSize():
                return
            self.screenSize = base.getSize()

            # Resize all editor frames
            self.menuBar.resizeFrame()

    # ------------------------------------------------------------------
    # SELECTION BOX
    # ------------------------------------------------------------------
    def startBoxDraw(self):
        """Start drawing the box"""
        if self.mouseWatcherNode.hasMouse():
            # get the mouse position
            self.startPos = LPoint2f(self.mouseWatcherNode.getMouse())
        taskMgr.add(self.dragBoxDrawTask, "dragBoxDrawTask")

    def stopBoxDraw(self):
        """Stop the draw box task and remove the box"""
        if not taskMgr.hasTaskNamed("dragBoxDrawTask"): return
        taskMgr.remove("dragBoxDrawTask")
        if self.startPos is None or self.lastPos is None: return
        self.nodeMgr.deselectAll()
        if self.box is not None:
            for node in self.nodeMgr.getAllNodes():
                # store some view scales for calculations
                viewXScale = self.viewNP.getScale().getX()
                viewZScale = self.viewNP.getScale().getZ()

                # calculate the node edges
                nodeLeft = node.getLeft() * viewXScale
                nodeRight = node.getRight() * viewXScale
                nodeBottom = node.getBottom() * viewZScale
                nodeTop = node.getTop() * viewZScale

                # calculate bounding box edges
                left = min(self.lastPos.getX(), self.startPos.getX())
                right = max(self.lastPos.getX(), self.startPos.getX())
                top = max(self.lastPos.getY(), self.startPos.getY())
                bottom = min(self.lastPos.getY(), self.startPos.getY())

                # check for hits
                xGood = yGood = False
                if left < nodeLeft and right > nodeLeft:
                    xGood = True
                elif left < nodeRight and right > nodeRight:
                    xGood = True
                if top > nodeTop and bottom < nodeTop:
                    yGood = True
                elif top > nodeBottom and bottom < nodeBottom:
                    yGood = True

                # check if we have any hits
                if xGood and yGood:
                    self.nodeMgr.selectNode(node, True, True)

            # Cleanup the selection box
            self.box.removeNode()
            self.startPos = None
            self.lastPos = None

    def dragBoxDrawTask(self, task):
        """This task will track the mouse position and actualize the box's size
        according to the first click position of the mouse"""
        if self.mouseWatcherNode.hasMouse():
            if self.startPos is None:
                self.startPos = LPoint2f(self.mouseWatcherNode.getMouse())
            # get the current mouse position
            self.lastPos = LPoint2f(self.mouseWatcherNode.getMouse())
        else:
            return task.cont

        # check if we already have a box
        if self.box != None:
            # if so, remove that old box
            self.box.removeNode()
        # set the box's size
        self.boxCardMaker.setFrame(self.lastPos.getX(), self.startPos.getX(),
                                   self.startPos.getY(), self.lastPos.getY())
        # generate, setup and draw the box
        node = self.boxCardMaker.generate()
        self.box = render2d.attachNewNode(node)
        self.box.setBin("gui-popup", 25)
        self.box.setTransparency(TransparencyAttrib.M_alpha)

        # run until the task is manually stopped
        return task.cont
class DirectGrid(NodePath, DirectObject):
    def __init__(self,parent,gridSize=100.0,gridSpacing=5.0,planeColor=(0.5,0.5,0.5,0.5)):
        # Initialize superclass
        NodePath.__init__(self)
        self.assign(hidden.attachNewNode('DirectGrid'))
        # Don't wireframe or light
        #useDirectRenderStyle(self)
        self.setLightOff(0)
        self.setRenderModeFilled()
        self.parent = parent

        # Load up grid parts to initialize grid object
        # Polygon used to mark grid plane
        self.gridBack = loader.loadModel('models/misc/gridBack.egg.pz')
        self.gridBack.reparentTo(self)
        self.gridBack.setColor(*planeColor)

        # Grid Lines
        self.lines = self.attachNewNode('gridLines')
        self.minorLines = LineNodePath(self.lines)
        self.minorLines.lineNode.setName('minorLines')
        self.minorLines.setColor(VBase4(0.3, 0.55, 1, 1))
        self.minorLines.setThickness(1)

        self.majorLines = LineNodePath(self.lines)
        self.majorLines.lineNode.setName('majorLines')
        self.majorLines.setColor(VBase4(0.3, 0.55, 1, 1))
        self.majorLines.setThickness(5)

        self.centerLines = LineNodePath(self.lines)
        self.centerLines.lineNode.setName('centerLines')
        self.centerLines.setColor(VBase4(1, 0, 0, 0))
        self.centerLines.setThickness(3)

        # Small marker to hilight snap-to-grid point
        self.snapMarker = loader.loadModel('models/misc/sphere.egg.pz')
        self.snapMarker.node().setName('gridSnapMarker')
        self.snapMarker.reparentTo(self)
        self.snapMarker.setColor(1, 0, 0, 1)
        self.snapMarker.setScale(0.3)
        self.snapPos = Point3(0)

        # Initialize Grid characteristics
        self.fXyzSnap = 1
        self.fHprSnap = 1
        self.gridSize = gridSize
        self.gridSpacing = gridSpacing
        self.snapAngle = 15.0
        self.enable()

    def enable(self):
        self.reparentTo(self.parent)
        self.updateGrid()
        self.fEnabled = 1

    def disable(self):
        self.reparentTo(hidden)
        self.fEnabled = 0

    def toggleGrid(self):
        if self.fEnabled:
            self.disable()
        else:
            self.enable()

    def isEnabled(self):
        return self.fEnabled

    def updateGrid(self):
        # Update grid lines based upon current grid spacing and grid size
        # First reset existing grid lines
        self.minorLines.reset()
        self.majorLines.reset()
        self.centerLines.reset()

        # Now redraw lines
        numLines = int(math.ceil(self.gridSize/self.gridSpacing))
        scaledSize = numLines * self.gridSpacing

        center = self.centerLines
        minor = self.minorLines
        major = self.majorLines
        for i in range(-numLines, numLines + 1):
            if i == 0:
                center.moveTo(i * self.gridSpacing, -scaledSize, 0)
                center.drawTo(i * self.gridSpacing, scaledSize, 0)
                center.moveTo(-scaledSize, i * self.gridSpacing, 0)
                center.drawTo(scaledSize, i * self.gridSpacing, 0)
            else:
                if (i % 5) == 0:
                    major.moveTo(i * self.gridSpacing, -scaledSize, 0)
                    major.drawTo(i * self.gridSpacing, scaledSize, 0)
                    major.moveTo(-scaledSize, i * self.gridSpacing, 0)
                    major.drawTo(scaledSize, i * self.gridSpacing, 0)
                else:
                    minor.moveTo(i * self.gridSpacing, -scaledSize, 0)
                    minor.drawTo(i * self.gridSpacing, scaledSize, 0)
                    minor.moveTo(-scaledSize, i * self.gridSpacing, 0)
                    minor.drawTo(scaledSize, i * self.gridSpacing, 0)

        center.create()
        minor.create()
        major.create()
        if (self.gridBack):
            self.gridBack.setScale(scaledSize)

    def setXyzSnap(self, fSnap):
        self.fXyzSnap = fSnap

    def getXyzSnap(self):
        return self.fXyzSnap

    def setHprSnap(self, fSnap):
        self.fHprSnap = fSnap

    def getHprSnap(self):
        return self.fHprSnap

    def computeSnapPoint(self, point):
        # Start of with current point
        self.snapPos.assign(point)
        # Snap if necessary
        if self.fXyzSnap:
            self.snapPos.set(
                ROUND_TO(self.snapPos[0], self.gridSpacing),
                ROUND_TO(self.snapPos[1], self.gridSpacing),
                ROUND_TO(self.snapPos[2], self.gridSpacing))

        # Move snap marker to this point
        self.snapMarker.setPos(self.snapPos)

        # Return the hit point
        return self.snapPos

    def computeSnapAngle(self, angle):
        return ROUND_TO(angle, self.snapAngle)

    def setSnapAngle(self, angle):
        self.snapAngle = angle

    def getSnapAngle(self):
        return self.snapAngle

    def setGridSpacing(self, spacing):
        self.gridSpacing = spacing
        self.updateGrid()

    def getGridSpacing(self):
        return self.gridSpacing

    def setGridSize(self, size):
        # Set size of grid back and redraw lines
        self.gridSize = size
        self.updateGrid()

    def getGridSize(self):
        return self.gridSize
class NodeEditor(DirectObject):
    def __init__(self, parent, customNodeMap={}, customExporterMap={}):

        DirectObject.__init__(self)

        fn = Filename.fromOsSpecific(os.path.dirname(__file__))
        fn.makeTrueCase()
        self.icon_dir = str(fn) + "/"
        loadPrcFileData("", f"model-path {self.icon_dir}")

        #
        # NODE VIEW
        #
        self.viewNP = aspect2d.attachNewNode("viewNP")
        self.viewNP.setScale(0.5)

        #
        # NODE MANAGER
        #
        self.nodeMgr = NodeManager(self.viewNP, customNodeMap)

        # Drag view
        self.mouseSpeed = 1
        self.mousePos = None
        self.startCameraMovement = False

        # Box select
        # variables to store the start and current pos of the mousepointer
        self.startPos = LPoint2f(0, 0)
        self.lastPos = LPoint2f(0, 0)
        # variables for the to be drawn box
        self.boxCardMaker = CardMaker("SelectionBox")
        self.boxCardMaker.setColor(1, 1, 1, 0.25)
        self.box = None

        #
        # MENU BAR
        #
        self.mainView = MainView(parent, customNodeMap, customExporterMap)

        self.enable_editor()

    # ------------------------------------------------------------------
    # FRAME COMPATIBILITY FUNCTIONS
    # ------------------------------------------------------------------
    def is_dirty(self):
        """
        This method returns True if an unsaved state of the editor is given
        """
        return len(self.nodeMgr.getAllNodes()) > 0

    def enable_editor(self):
        """
        Enable the editor.
        """
        self.enable_events()

        # Task for handling dragging of the camera/view
        taskMgr.add(self.updateCam,
                    "NodeEditor_task_camActualisation",
                    priority=-4)

        self.viewNP.show()
        self.nodeMgr.showConnections()

    def disable_editor(self):
        """
        Disable the editor.
        """
        self.ignore_all()
        taskMgr.remove("NodeEditor_task_camActualisation")

        self.viewNP.hide()
        self.nodeMgr.hideConnections()

    def do_exception_save(self):
        """
        Save content of editor if the application crashes
        """
        Save(self.nodeMgr.nodeList, self.nodeMgr.connections, True)

    # ------------------------------------------------------------------
    # NODE EDITOR RELATED EVENTS
    # ------------------------------------------------------------------
    def enable_events(self):
        # Add nodes
        self.accept("addNode", self.nodeMgr.addNode)
        # Remove nodes
        self.accept("NodeEditor_removeNode", self.nodeMgr.removeNode)
        self.accept("x", self.nodeMgr.removeNode)
        self.accept("delete", self.nodeMgr.removeNode)
        # Selecting
        self.accept("selectNode", self.nodeMgr.selectNode)
        # Deselecting
        self.accept("mouse3", self.nodeMgr.deselectAll)
        # Node Drag and Drop
        self.accept("dragNodeStart", self.setDraggedNode)
        self.accept("dragNodeMove", self.updateNodeMove)
        self.accept("dragNodeStop", self.updateNodeStop)
        # Duplicate/Copy nodes
        self.accept("shift-d", self.nodeMgr.copyNodes)
        self.accept("NodeEditor_copyNodes", self.nodeMgr.copyNodes)
        # Refresh node logics
        self.accept("ctlr-r", self.nodeMgr.updateAllLeaveNodes)
        self.accept("NodeEditor_refreshNodes",
                    self.nodeMgr.updateAllLeaveNodes)

        #
        # SOCKET RELATED EVENTS
        #
        self.accept("updateConnectedNodes", self.nodeMgr.updateConnectedNodes)
        # Socket connection with drag and drop
        self.accept("startPlug", self.nodeMgr.setStartPlug)
        self.accept("endPlug", self.nodeMgr.setEndPlug)
        self.accept("connectPlugs", self.nodeMgr.connectPlugs)
        self.accept("cancelPlug", self.nodeMgr.cancelPlug)
        # Draw line while connecting sockets
        self.accept("startLineDrawing", self.startLineDrawing)
        self.accept("stopLineDrawing", self.stopLineDrawing)

        #
        # CONNECTION RELATED EVENTS
        #
        self.accept("NodeEditor_updateConnections",
                    self.nodeMgr.updateConnections)

        #
        # PROJECT MANAGEMENT
        #
        self.accept("NodeEditor_new", self.newProject)
        self.accept("NodeEditor_save", self.saveProject)
        self.accept("NodeEditor_load", self.loadProject)
        self.accept("quit_app", exit)

        #
        # EXPORTERS
        #
        self.accept("NodeEditor_customSave", self.customExport)

        #
        # EDITOR VIEW
        #
        # Zooming
        self.accept("NodeEditor_zoom", self.zoom)
        self.accept("NodeEditor_zoom_reset", self.zoomReset)
        self.accept("wheel_up", self.zoom, [True])
        self.accept("wheel_down", self.zoom, [False])

        # Drag view
        self.accept("mouse2", self.setMoveCamera, [True])
        self.accept("mouse2-up", self.setMoveCamera, [False])

        # Box select
        # accept the 1st mouse button events to start and stop the draw
        self.accept("mouse1", self.startBoxDraw)
        self.accept("mouse1-up", self.stopBoxDraw)

    # ------------------------------------------------------------------
    # PROJECT FUNCTIONS
    # ------------------------------------------------------------------
    def newProject(self):
        self.nodeMgr.cleanup()

    def saveProject(self):
        Save(self.nodeMgr.nodeList, self.nodeMgr.connections)

    def loadProject(self):
        self.nodeMgr.cleanup()
        Load(self.nodeMgr)

    def customExport(self, exporter):
        exporter(self.nodeMgr.nodeList, self.nodeMgr.connections)

    # ------------------------------------------------------------------
    # CAMERA SPECIFIC FUNCTIONS
    # ------------------------------------------------------------------
    def setMoveCamera(self, moveCamera):
        """Start dragging around the editor area/camera"""
        # store the mouse position if weh have a mouse
        if base.mouseWatcherNode.hasMouse():
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
            self.mousePos = Point2(x, y)
        # set the variable according to if we want to move the camera or not
        self.startCameraMovement = moveCamera

    def updateCam(self, task):
        """Task that will move the editor area/camera around according
        to mouse movements"""
        # variables to store the mouses current x and y position
        x = 0.0
        y = 0.0
        if base.mouseWatcherNode.hasMouse():
            # get the mouse position
            x = base.mouseWatcherNode.getMouseX()
            y = base.mouseWatcherNode.getMouseY()
        if base.mouseWatcherNode.hasMouse() \
        and self.mousePos is not None \
        and self.startCameraMovement:
            # Move the viewer node aspect independent
            wp = base.win.getProperties()
            aspX = 1.0
            aspY = 1.0
            wpXSize = wp.getXSize()
            wpYSize = wp.getYSize()
            if wpXSize > wpYSize:
                aspX = wpXSize / float(wpYSize)
            else:
                aspY = wpYSize / float(wpXSize)
            mouseMoveX = (self.mousePos.getX() - x) / self.viewNP.getScale(
            ).getX() * self.mouseSpeed * aspX
            mouseMoveY = (self.mousePos.getY() - y) / self.viewNP.getScale(
            ).getZ() * self.mouseSpeed * aspY
            self.mousePos = Point2(x, y)

            self.viewNP.setX(self.viewNP, -mouseMoveX)
            self.viewNP.setZ(self.viewNP, -mouseMoveY)

            self.nodeMgr.updateConnections()

        # continue the task until it got manually stopped
        return task.cont

    def zoom(self, zoomIn):
        """Zoom the editor in or out dependent on the value in zoomIn"""
        zoomFactor = 0.05
        maxZoomIn = 2
        maxZoomOut = 0.1
        if zoomIn:
            s = self.viewNP.getScale()
            if s.getX() - zoomFactor < maxZoomIn and s.getY(
            ) - zoomFactor < maxZoomIn and s.getZ() - zoomFactor < maxZoomIn:
                self.viewNP.setScale(s.getX() + zoomFactor,
                                     s.getY() + zoomFactor,
                                     s.getZ() + zoomFactor)
        else:
            s = self.viewNP.getScale()
            if s.getX() - zoomFactor > maxZoomOut and s.getY(
            ) - zoomFactor > maxZoomOut and s.getZ() - zoomFactor > maxZoomOut:
                self.viewNP.setScale(s.getX() - zoomFactor,
                                     s.getY() - zoomFactor,
                                     s.getZ() - zoomFactor)
        self.nodeMgr.updateConnections()

    def zoomReset(self):
        """Set the zoom level back to the default"""
        self.viewNP.setScale(0.5)
        self.nodeMgr.updateConnections()

    # ------------------------------------------------------------------
    # DRAG LINE
    # ------------------------------------------------------------------
    def startLineDrawing(self, startPos):
        """Start a task that will draw a line from the given start
        position to the cursor"""
        self.line = LineNodePath(render2d,
                                 thickness=2,
                                 colorVec=(0.8, 0.8, 0.8, 1))
        self.line.moveTo(startPos)
        t = taskMgr.add(self.drawLineTask, "drawLineTask")
        t.startPos = startPos

    def drawLineTask(self, task):
        """Draws a line from a given start position to the cursor"""
        mwn = base.mouseWatcherNode
        if mwn.hasMouse():
            pos = Point3(mwn.getMouse()[0], 0, mwn.getMouse()[1])

            self.line.reset()
            self.line.moveTo(task.startPos)
            self.line.drawTo(pos)
            self.line.create()
        return task.cont

    def stopLineDrawing(self):
        """Stop the task that draws a line to the cursor"""
        taskMgr.remove("drawLineTask")
        if self.line is not None:
            self.line.reset()
            self.line = None

    # ------------------------------------------------------------------
    # EDITOR NODE DRAGGING UPDATE
    # ------------------------------------------------------------------
    def setDraggedNode(self, node):
        """This will set the node that is currently dragged around
        as well as update other selected nodes which will be moved
        in addition to the main dragged node"""
        self.draggedNode = node
        self.draggedNode.disable()
        self.tempNodePositions = {}
        for node in self.nodeMgr.selectedNodes:
            self.tempNodePositions[node] = node.frame.getPos(render2d)

    def updateNodeMove(self, mouseA, mouseB):
        """Will be called as long as a node is beeing dragged around"""
        for node in self.nodeMgr.selectedNodes:
            if node is not self.draggedNode and node in self.tempNodePositions.keys(
            ):
                editVec = Vec3(self.tempNodePositions[node] - mouseA)
                newPos = mouseB + editVec
                node.frame.setPos(render2d, newPos)
        self.nodeMgr.updateConnections()

    def updateNodeStop(self, node=None):
        """Will be called when a node dragging stopped"""
        self.draggedNode.enable()
        self.draggedNode = None
        self.tempNodePositions = {}
        self.nodeMgr.updateConnections()

    # ------------------------------------------------------------------
    # SELECTION BOX
    # ------------------------------------------------------------------
    def startBoxDraw(self):
        """Start drawing the box"""
        if base.mouseWatcherNode.hasMouse():
            # get the mouse position
            self.startPos = LPoint2f(base.mouseWatcherNode.getMouse())
        taskMgr.add(self.dragBoxDrawTask, "dragBoxDrawTask")

    def stopBoxDraw(self):
        """Stop the draw box task and remove the box"""
        if not taskMgr.hasTaskNamed("dragBoxDrawTask"): return
        taskMgr.remove("dragBoxDrawTask")
        if self.startPos is None or self.lastPos is None: return
        self.nodeMgr.deselectAll()
        if self.box is not None:
            for node in self.nodeMgr.getAllNodes():
                # store some view scales for calculations
                viewXScale = self.viewNP.getScale().getX()
                viewZScale = self.viewNP.getScale().getZ()

                # calculate the node edges
                nodeLeft = node.getLeft() * viewXScale
                nodeRight = node.getRight() * viewXScale
                nodeBottom = node.getBottom() * viewZScale
                nodeTop = node.getTop() * viewZScale

                # calculate bounding box edges
                left = min(self.lastPos.getX(), self.startPos.getX())
                right = max(self.lastPos.getX(), self.startPos.getX())
                top = max(self.lastPos.getY(), self.startPos.getY())
                bottom = min(self.lastPos.getY(), self.startPos.getY())

                # check for hits
                xGood = yGood = False
                if left < nodeLeft and right > nodeLeft:
                    xGood = True
                elif left < nodeRight and right > nodeRight:
                    xGood = True
                if top > nodeTop and bottom < nodeTop:
                    yGood = True
                elif top > nodeBottom and bottom < nodeBottom:
                    yGood = True

                # check if we have any hits
                if xGood and yGood:
                    self.nodeMgr.selectNode(node, True, True)

            # Cleanup the selection box
            self.box.removeNode()
            self.startPos = None
            self.lastPos = None

    def dragBoxDrawTask(self, task):
        """This task will track the mouse position and actualize the box's size
        according to the first click position of the mouse"""
        if base.mouseWatcherNode.hasMouse():
            if self.startPos is None:
                self.startPos = LPoint2f(base.mouseWatcherNode.getMouse())
            # get the current mouse position
            self.lastPos = LPoint2f(base.mouseWatcherNode.getMouse())
        else:
            return task.cont

        # check if we already have a box
        if self.box != None:
            # if so, remove that old box
            self.box.removeNode()
        # set the box's size
        self.boxCardMaker.setFrame(self.lastPos.getX(), self.startPos.getX(),
                                   self.startPos.getY(), self.lastPos.getY())
        # generate, setup and draw the box
        node = self.boxCardMaker.generate()
        self.box = render2d.attachNewNode(node)
        self.box.setBin("gui-popup", 25)
        self.box.setTransparency(TransparencyAttrib.M_alpha)

        # run until the task is manually stopped
        return task.cont
Exemple #11
0
class Ball:

    NAME_DEFAULT = "UNNAMED"
    POS_DEFAULT = (0, 0, 5)
    SCALE_DEFAULT = (1, 1, 1)
    HPR_DEFAULT = (0, 0, 0)
    MODEL_EGG_DEFAULT = "../egg/paahahmo.egg"
    JUMP_SOUND_DEFAULT = "../media/jump_sound.wav"
    BOUNCE_SOUND_DEFAULT = "../media/bounce_sound.wav"

    BALL_BODY_MASS_WEIGHT = 1000
    BALL_BODY_MASS_RADIUS = 1
    FORCE = 90000
    TORQUE = 3000

    MOVEMENT_DEBUG = 0
    JUMP_DEBUG = 0
    STATIC_JUMP = 0
    STATIC_JUMP_FORCE = 2800000
    JUMP_FORCE = 120000
    MAX_JUMP_REACH_TIME = 0.7
    COLLISION_THRESHOLD_TIME = 0.33
    MAX_MOVEMENT_SPEED = 20.0

    def __init__(
        self,
        world,
        space,
        name=NAME_DEFAULT,
        modelEgg=MODEL_EGG_DEFAULT,
        pos=POS_DEFAULT,
        scale=SCALE_DEFAULT,
        hpr=HPR_DEFAULT,
    ):
        self.limitedYMovement = False
        self.limitedYCoords = [-1, -1]
        self.limitedZMovement = False
        self.limitedZCoords = [-1, -1]
        self.name = name
        self.pos = pos
        self.hpr = hpr
        self.scale = scale
        self.world = world
        self.space = space
        self.jumping = False
        self.jumpStarted = 0.0
        self.jumpLastUpdate = 0.0
        self.lastCollisionTime = 0.0
        self.lastCollisionIsGround = True
        self.lastGroundCollisionBodyPos = None
        self.jumpSound = loader.loadSfx(self.JUMP_SOUND_DEFAULT)
        self.bounceSound = loader.loadSfx(self.BOUNCE_SOUND_DEFAULT)

        if Ball.MOVEMENT_DEBUG:
            self.lastDrawTime = 0.0
            self.lastDrawTime2 = 0.0
            self.lines = LineNodePath(parent=render,
                                      thickness=3.0,
                                      colorVec=Vec4(1, 0, 0, 1))
            self.lines2 = LineNodePath(parent=render,
                                       thickness=3.0,
                                       colorVec=Vec4(0, 0, 1, 1))
            self.lines3 = LineNodePath(parent=render,
                                       thickness=3.0,
                                       colorVec=Vec4(0, 1, 0, 1))
        if Ball.JUMP_DEBUG:
            self.lastTakeoffTime = 0.0
        self.moveLeft = False
        self.moveRight = False
        self.modelNode = self.createModelNode(self.pos, self.hpr, self.scale,
                                              modelEgg)
        self.ballBody = self.createBallBody(self.modelNode, self.world)
        self.ballGeom = self.createBallGeom(self.modelNode, self.ballBody,
                                            self.space)
        self.modelNode.reparentTo(render)

    def createModelNode(self, pos, hpr, scale, modelEgg):
        modelNode = loader.loadModel(modelEgg)
        modelNode.setPos(pos)
        modelNode.setHpr(hpr)
        modelNode.setScale(scale)
        return modelNode

    def createBallBody(self, modelNode, world):
        ballBody = OdeBody(world)
        M = OdeMass()
        M.setSphere(Ball.BALL_BODY_MASS_WEIGHT, Ball.BALL_BODY_MASS_RADIUS)
        ballBody.setMass(M)
        ballBody.setPosition(modelNode.getPos(render))
        ballBody.setQuaternion(modelNode.getQuat(render))
        return ballBody

    def createBallGeom(self, modelNode, ballBody, space):
        ballGeom = OdeSphereGeom(space, 1)
        ballGeom.setCollideBits(BitMask32(0x2))
        ballGeom.setCategoryBits(BitMask32(0x1))
        ballGeom.setBody(ballBody)
        space.setSurfaceType(ballGeom, SurfaceType.BALL)
        return ballGeom

    def getDefaultGravityVec3(self):
        out = VBase3()
        out.setX(0.0)
        out.setY(0.0)
        out.setZ(-9.8)
        return out

    def angleVec3(self, v1, v2):
        return math.acos(
            self.dotProductVec3(v1, v2) / (v1.length() * v2.length()))

    def dotProductVec3(self, v1, v2):
        return v1.getX() * v2.getX() + v1.getY() * v2.getY() + v1.getZ(
        ) * v2.getZ()

    def moveBall(self, normalGravity, func):
        g = self.world.getGravity()
        angle = self.angleVec3(g, self.getDefaultGravityVec3())
        if not (angle > math.pi / 3.99 and angle < math.pi / 1.99):
            if normalGravity:
                func()
        else:
            if not normalGravity:
                func()
        return

    def arrowRightDown(self):
        self.moveBall(1, self.startMoveRight)

    def arrowRightUp(self):
        self.moveBall(1, self.stopMoveRight)

    def arrowLeftDown(self):
        self.moveBall(1, self.startMoveLeft)

    def arrowLeftUp(self):
        self.moveBall(1, self.stopMoveLeft)

    def arrowUpDown(self):
        self.moveBall(0, self.startMoveRight)

    def arrowUpUp(self):
        self.moveBall(0, self.stopMoveRight)

    def arrowDownDown(self):
        self.moveBall(0, self.startMoveLeft)

    def arrowDownUp(self):
        self.moveBall(0, self.stopMoveLeft)

    def startMoveLeft(self):
        self.moveLeft = True

    def stopMoveLeft(self):
        self.moveLeft = False

    def isMovingLeft(self):
        return self.moveLeft

    def startMoveRight(self):
        self.moveRight = True

    def stopMoveRight(self):
        self.moveRight = False

    def isMovingRight(self):
        return self.moveRight

    def perpendicularUnitVec3WithFixedX(self, v):
        out = VBase3()
        a = 1.0
        out.setX(0.0)
        if v.getY() != 0.0:
            out.setY(-(v.getZ() * a) / v.getY())
            out.setZ(a)
        else:
            out.setY(a)
            out.setZ(0.0)
        out /= out.length()
        return out

    def playSound(self, name, override):
        if name == "jump":
            if self.jumpSound != None:
                if override == False and self.jumpSound.status() != 1:
                    self.jumpSound.stop()
                self.jumpSound.play()
        if name == "bounce":
            if self.bounceSound != None:
                if override == False and self.bounceSound.status() != 1:
                    self.bounceSound.stop()
                self.bounceSound.play()
        return

    def jumpOn(self):
        if self.isColliding():
            self.playSound("jump", False)
        if self.isColliding() == True and self.lastCollisionIsGround:
            self.jumping = True
            self.jumpStarted = globalClock.getLongTime()
        return

    def jumpOff(self):
        self.jumping = False
        self.jumpStarted = 0.0
        self.jumpLastUpdate = 0.0
        return

    def isColliding(self):
        if self.lastCollisionTime == 0.0:
            return False

        now = globalClock.getLongTime()
        interval = now - self.lastCollisionTime

        if interval < Ball.COLLISION_THRESHOLD_TIME:
            if Ball.JUMP_DEBUG:
                self.lastTakeoffTime = 0.0
            return True
        else:
            if Ball.JUMP_DEBUG:
                # Draw debug info
                if self.lastTakeoffTime == 0.0:
                    self.lastTakeoffTime = now
                    messenger.send('updateHUD', [", Ball state: AIR"])
            return False

    def isGroundCollision(self, bodyPos, colPos):
        # Tolerance should probably be some fraction of the radius
        g = self.world.getGravity()
        g /= g.length()
        # g *= Ball.RADIUS
        bodyPos[0] = bodyPos[0] + g.getX()
        bodyPos[1] = bodyPos[1] + g.getY()
        bodyPos[2] = bodyPos[2] + g.getZ()
        return self.areCloseEnough(bodyPos, colPos)

    def areCloseEnough(self, pos1, pos2):
        tolerance = 0.5
        dy = pos1[1] - pos2[1]
        # >= is important
        if (dy >= 0.0 and dy < tolerance) or (dy < 0.0 and dy > -tolerance):
            dz = pos1[2] - pos2[2]
            # >= is important
            if (dz >= 0.0 and dz < tolerance) or (dz < 0.0
                                                  and dz > -tolerance):
                return True
        return False

    def refreshCollisionTime(self, collisionEntry):
        body = self.ballBody
        pos = body.getPosition()
        now = globalClock.getLongTime()

        if self.isColliding() == False:
            self.playSound("bounce", False)
        '''
		Only "sample" collisions occasionally, should be enough for jumping.
		This also effects the collision threshold time, which should be somewhat
		larger (maybe twice?) than this value for jumping to work properly
		'''

        if now - self.lastCollisionTime < 0.15:
            return

        self.lastCollisionTime = now

        if Ball.JUMP_DEBUG:
            previous = self.lastCollisionIsGround

        self.lastCollisionIsGround = False
        n = collisionEntry.getNumContacts()

        for i in range(n):
            p = collisionEntry.getContactPoint(i)
            if Ball.MOVEMENT_DEBUG and now - self.lastDrawTime > 0.1:
                self.lines2.reset()
                x = p.getX(
                ) + 1.2  # This will bring the line in front of the ball
                y = p.getY()
                z = p.getZ()
                self.lines2.drawLines([
                    ((x, y, z), (x, y - 1.0, z + 2.0))
                ])  # "marker" will be a line upwards tilting to the left
                self.lines2.create()
                self.lastDrawTime = now
            if self.isGroundCollision(pos, p):
                self.lastCollisionIsGround = True
                self.lastGroundCollisionBodyPos = pos
                break

        if not self.lastCollisionIsGround and self.lastGroundCollisionBodyPos != None:
            if self.areCloseEnough(pos, self.lastGroundCollisionBodyPos):
                self.lastCollisionIsGround = True
                # Position should not be updated, since this was not technically a ground collision
                # as we normally judge them

        if Ball.JUMP_DEBUG:
            # Draw debug info
            if previous != self.lastCollisionIsGround or self.lastTakeoffTime != 0.0:
                self.lastTakeoffTime = 0.0
                if self.lastCollisionIsGround:
                    messenger.send('updateHUD', [", Ball state: GROUND"])
                else:
                    messenger.send('updateHUD', [", Ball state: ???"])

    def haveRoughlySameDirection(self, ivec, ivec2):
        if ivec.length() == 0 or ivec2.length() == 0:
            return False
        angle = self.angleVec3(ivec, ivec2)
        #print angle
        if (-math.pi / 8.0) < angle and angle < (math.pi / 8.0):
            return True
        return False

    def limitYMovement(self, ymin, ymax):
        if ymin >= ymax:
            return
        self.limitedYMovement = True
        self.limitedYCoords = [ymin, ymax]

    def limitZMovement(self, zmin, zmax):
        if zmin >= zmax:
            return
        self.limitedZMovement = True
        self.limitedZCoords = [zmin, zmax]

    def updateModelNode(self):
        ''' Update objects after one physics iteration '''
        now = globalClock.getLongTime()
        body = self.ballBody
        g = self.world.getGravity()

        if self.limitedYMovement:
            if body.getPosition().getY() > self.limitedYCoords[1]:
                self.ballBody.setLinearVel(Vec3(0, -10, 0))
            if body.getPosition().getY() < self.limitedYCoords[0]:
                self.ballBody.setLinearVel(Vec3(0, 10, 0))
        if self.limitedZMovement:
            if body.getPosition().getZ() > self.limitedZCoords[1]:
                messenger.send(EventType.RESTART)
                return
            if body.getPosition().getZ() < self.limitedZCoords[0]:
                messenger.send(EventType.RESTART)
                return

        if Ball.MOVEMENT_DEBUG and now - self.lastDrawTime2 > 0.2:
            v = body.getLinearVel()
            v2 = self.perpendicularUnitVec3WithFixedX(v)
            self.lines.reset()
            self.lines3.reset()
            x = body.getPosition().getX(
            ) + 1.2  # This will bring the line in front of the ball
            y = body.getPosition().getY()
            z = body.getPosition().getZ()
            self.lines.drawLines([((x, y, z), (x + v.getX(), y + v.getY(),
                                               z + v.getZ()))])
            self.lines3.drawLines([((x, y, z), (x + v2.getX(), y + v2.getY(),
                                                z + v2.getZ()))])
            self.lines.create()
            self.lines3.create()
            self.lastDrawTime2 = 0.0
        ''' Can move better when on (touching) something, moving in the air is harder '''
        divisor = 3.5
        if self.isColliding() and self.lastCollisionIsGround:
            divisor = 1.0

        if self.moveLeft or self.moveRight:
            factor = 1.0
            if self.moveLeft:
                factor = -1.0

            # Limit speed to some constant
            v3 = self.perpendicularUnitVec3WithFixedX(g)
            v3 *= factor * Ball.FORCE / divisor
            lv = self.ballBody.getLinearVel()
            lv.setX(0.0)
            lv.setZ(0.0)
            if self.haveRoughlySameDirection(
                    lv, v3) and abs(lv.length()) > self.MAX_MOVEMENT_SPEED:
                factor = 0.0

            v3 = self.perpendicularUnitVec3WithFixedX(g)
            v3 *= factor * Ball.FORCE / divisor

            self.ballBody.setForce(y=v3.getY(), x=v3.getX(), z=v3.getZ())
            v3 = self.perpendicularUnitVec3WithFixedX(g)
            v3 *= factor * Ball.TORQUE / divisor
            self.ballBody.setTorque(y=v3.getY(), x=v3.getX(), z=v3.getX())
        ''' This is still really crappy, will revise later '''
        if self.jumping == True:
            g = -g
            g /= g.length()
            if Ball.STATIC_JUMP:
                g *= Ball.STATIC_JUMP_FORCE
                self.ballBody.setForce(y=g.getY(), x=g.getX(), z=g.getZ())
                self.jumping = False
            else:
                elapsed = now - self.jumpStarted
                if elapsed > 0.0 and elapsed < Ball.MAX_JUMP_REACH_TIME:
                    g *= Ball.JUMP_FORCE
                    self.ballBody.setForce(y=g.getY(), x=g.getX(), z=g.getZ())
                elif elapsed > Ball.MAX_JUMP_REACH_TIME:
                    self.jumping = False

        # Keep the body in 2d position
        self.alignBodyTo2d()

        # Set the new position
        self.modelNode.setPos(render, self.ballBody.getPosition())
        self.modelNode.setQuat(render, Quat(self.ballBody.getQuaternion()))

    def alignBodyTo2d(self):
        body = self.ballBody

        # Constrain position of the body
        oldPos = body.getPosition()
        newPos = oldPos
        newPos[0] = 0
        newPos[1] = oldPos[1]
        newPos[2] = oldPos[2]

        # Constrain quaternion of the body
        oldQuat = body.getQuaternion()
        newQuat = oldQuat
        newQuat[0] = oldQuat[0]
        newQuat[1] = oldQuat[1]
        newQuat[2] = 0
        newQuat[3] = 0

        # Set new position and quaternion of the body
        body.setPosition(newPos)
        body.setQuaternion(Quat(newQuat))

    def getPosition(self):
        return self.ballBody.getPosition()

    def setPosition(self, pos):
        self.ballBody.setPosition(pos)

    def getBody(self):
        return self.ballBody

    def getModelNode(self):
        return self.modelNode

    def removeNode(self):
        self.modelNode.removeNode()
Exemple #12
0
class Planet(SphericalBody): 
    '''
    Planet contains units and structures
    '''

    def __init__(self, orbital_radius, orbital_angle, radius, parent_star, prev_planet=None, player=None):
        '''
        Constructor for class planet.
        @param position: Point3D, position in space
        @param radius: float, body radius
        @param player: Player, the owner of the planet
        @param parent_star: Star, the specific star the planet is orbiting around
        @param prev_planet: previous planet in the star system, if None then the planet is the first
        '''
        position = Point3(orbital_radius * math.cos(orbital_angle),
                          orbital_radius * math.sin(orbital_angle), 0)
        super(Planet, self).__init__(position, radius, False, player)
        self.orbital_velocity = 0
        self.max_orbital_velocity = 0
        self.orbital_radius = orbital_radius
        self.orbital_angle = orbital_angle
        self.parent_star = parent_star
        self.prev_planet = prev_planet
        self.next_planet = None
        self._orbiting_units = []
        self._surface_structures = []
        self.__initSceneGraph()
               
        '''For the Player'''
        self.task_structure_timer = None
        self.task_unit_timer = None
        '''For the AI'''
        self.task_structure_timers = []
        self.task_unit_timers = []
        
    def __initSceneGraph(self):

        #load various texture stages of the planet
        self.forge_tex = TextureStage('forge')
        self.forge_tex.setMode(TextureStage.MDecal)
        self.nexus_tex = TextureStage('nexus')
        self.nexus_tex.setMode(TextureStage.MDecal)
        self.extractor_phylon_ge_tex = TextureStage('extractor_phylon_ge')
        self.extractor_phylon_ge_tex.setMode(TextureStage.MDecal)
        
        # Parent node for relative position (no scaling)
        self.point_path = self.parent_star.point_path.attachNewNode("planet_node")
        self.point_path.setPos(self.position)
        
        #Models & textures
        self.model_path = loader.loadModel("models/planets/planet_sphere")
        self.model_path.setTexture(SphericalBody.dead_planet_tex, 1)
        self.model_path.reparentTo(self.point_path)
        self.model_path.setScale(self.radius)
        self.model_path.setPythonTag('pyPlanet', self);
        
        cnode = CollisionNode("coll_sphere_node")
        cnode.setTag('planet', str(id(self)))
        #We use no displacement (0,0,0) and no scaling factor (1)
        cnode.addSolid(CollisionSphere(0,0,0,1))
        cnode.setIntoCollideMask(BitMask32.bit(1))
        # Reparenting the collision sphere so that it 
        # matches the planet perfectly.
        self.cnode_path = self.model_path.attachNewNode(cnode)
        
        self.lines = LineNodePath(parent = self.parent_star.point_path, thickness = 4.0, colorVec = Vec4(1.0, 1.0, 1.0, 0.2))
        self.quad_path = None
        
    def setTexture(self, structureType):
        '''
        Used whenever a structure is built on the planet
        @ StructureType is a string specifying the type of the structure
        '''
        if(structureType == "forge"):
            #SphericalBody.planet_forge_tex.setWrapU(Texture.WMInvalid)
            #SphericalBody.planet_forge_tex.setWrapV(Texture.WMInvalid)
            self.model_path.setTexture(self.forge_tex, SphericalBody.planet_forge_tex)
            #self.model_path.getTexture().setWrapU(Texture.WMClamp)
            #self.model_path.getTexture().setWrapV(Texture.WMClamp)
            self.model_path.setTexOffset(self.forge_tex, 0, 0)
            #self.model_path.setTexScale(self.forge_tex, -4, -2)
        elif(structureType == "nexus"):
            self.model_path.setTexture(self.nexus_tex, SphericalBody.planet_nexus_tex)
            self.model_path.setTexOffset(self.nexus_tex, 0, 10)
        elif(structureType == "extractor"):
            self.model_path.setTexture(self.extractor_phylon_ge_tex, SphericalBody.planet_extractor_tex)
            self.model_path.setTexOffset(self.extractor_phylon_ge_tex, 0, 20)
        elif(structureType == "phylon"):
            self.model_path.setTexture(self.extractor_phylon_ge_tex, SphericalBody.planet_phylon_tex)
            self.model_path.setTexOffset(self.extractor_phylon_ge_tex, 0, 20)
        elif(structureType == "generatorCore"):
            self.model_path.setTexture(self.extractor_phylon_ge_tex, SphericalBody.planet_generatorCore_tex)
            self.model_path.setTexOffset(self.extractor_phylon_ge_tex, 0, 20)
    
    def activateHighlight(self, thin):
        if thin:
            flare_tex = base.loader.loadTexture("models/billboards/thin_ring.png")
        else:
            flare_tex = base.loader.loadTexture("models/billboards/ring.png")
        cm = CardMaker('quad')
        cm.setFrameFullscreenQuad() # so that the center acts as the origin (from -1 to 1)
        self.quad_path = self.point_path.attachNewNode(cm.generate())        
        self.quad_path.setTransparency(TransparencyAttrib.MAlpha)
        self.quad_path.setTexture(flare_tex)
        if thin:
            self.quad_path.setColor(Vec4(1,1,1, 1))
        else:
            self.quad_path.setColor(Vec4(0.2, 0.3, 1.0, 1))
        self.quad_path.setScale(5)
        self.quad_path.setPos(Vec3(0,0,0))
        self.quad_path.setBillboardPointEye()
    
    def deactivateHighlight(self):
        if self.quad_path:
            self.quad_path.detachNode()
            self.quad_path = None
    
    def select(self, player):
        '''
        This method observes the events on the planet and calls the related methods
        and notifies the corresponding objects based on the state of the planet
        @param player, the player who has selected
        '''
        player.selected_star = None
        
        if(not self.activated and player.selected_planet == self):
            if((self.prev_planet == None or self.prev_planet.activated) and \
                    self.parent_star.activated and self.parent_star.player == player):
                self.activatePlanet(player)
        else:
#            from gameEngine.gameEngine import all_stars 
#            for star in all_stars:
#                star.deactivateHighlight()
            from gameEngine.gameEngine import all_planets
            for planet in all_planets:
                if(self != planet or self.next_planet != planet or self.prev_planet != planet):
                    planet.deactivateHighlight()
            if self.next_planet != None: self.next_planet.activateHighlight(True) 
            if self.prev_planet != None: self.prev_planet.activateHighlight(True)
            self.activateHighlight(False)
        
        player.selected_planet = self
        
        from gameEngine.gameEngine import updateGUI
        updateGUI.refreshUnitsAndConstructions(self)
        
    def selectRight(self, player):
        '''
        This method observes the events on the planet and calls the related methods
        and notifies the corresponding objects based on the state of the planet
        @param player, the player who has selected with right mouse click
        '''
        move_occured = False
        for selected_unit in player.selected_units:
            if(selected_unit != None and selected_unit.player == player):
                '''movement is inside the solar system'''
                if(selected_unit.host_planet.parent_star == self.parent_star):
                    if(selected_unit.host_planet == self.prev_planet):
                        selected_unit.moveUnitNext()
                        move_occured = True
                    if(selected_unit.host_planet == self.next_planet):
                        selected_unit.moveUnitPrev()   
                        move_occured = True 
                else:
                    '''movement is between solar systems in deep space'''
                    if(self.next_planet == None and selected_unit.host_planet.next_planet == None):
                        selected_unit.moveDeepSpace(self)
                        move_occured = True

        if(len(player.selected_units)!=0 and move_occured):
            if(player.selected_units[0] != None and player.selected_units[0].move_unit != None):
                base.sfxManagerList[0].update()
                player.selected_units[0].move_unit.play()
            
    def activatePlanet(self, player):
        '''
        Activates a constructed dead planet object, starting the orbital movement with the assigned value while
        the Game Engine calls the graphic engine to display the corresponding animation.
        @param player: Player, the player who controls the planet
        @precondition: MIN_PLANET_VELOCITY < orbital_velocity < MAX_PLANET_VELOCITY
        '''
        self.activated = True
        player.planets.append(self)
        self.player = player
        
        
        planet_created_sound = base.loader.loadSfx("sound/effects/planet/planetCreation.wav")
        planet_created_sound.setVolume(0.3)
        planet_created_sound.play()
#        base.sfxManagerList[0].update()
#        SphericalBody.planet_created_sound.play()
        
        self.radius = MAX_PLANET_RADIUS
        self.model_path.setScale(self.radius)
        
        '''TODO : display planet creation animation '''
        
        #rand = random.randrange(1,8,1)
        self.model_path.setTexture(SphericalBody.planet_activated_tex, 1)
        self.startSpin()
        
        # We want to avoid adding this task when the 'collapseOrbit' is already running
        if self.parent_star.lifetime != 0:
            taskMgr.add(self._accelerateOrbit, 'accelerateOrbit')
        
        self.orbit_path = shapes.makeArc(self.player.color, 360, int(self.orbital_radius))
        self.orbit_path.reparentTo(self.parent_star.point_path)
        self.orbit_path.setScale(self.orbital_radius)
        
        from gameModel.ai import AI
        if(type(self.player) != AI):
            from gameEngine.gameEngine import updateGUI
            updateGUI.refreshUnitsAndConstructions(self)
    
    def startSpin(self):
        self.day_period = self.model_path.hprInterval(PLANET_SPIN_VELOCITY, Vec3(360, 0, 0))
        self.day_period.loop()
    
    def startCollapse(self):
        try:
            if self.orbit_task:
                taskMgr.remove(self.orbit_task)
        except AttributeError:
            pass # no orbit on this planet (has not been activated)
        
        taskMgr.add(self._collapseOrbit, 'collapseOrbit')
#        taskMgr.setupTaskChain('collapseChain')
#        taskMgr.add(self._collapseOrbit, 'collapseOrbit')#, taskChain='collapseChain')
#        taskMgr.add(self._consume, 'consumePlanet', taskChain='collapseChain')
#        self.orbitTask = None
    
    def _consume(self):
        self._consumeUnits()
        self._consumeStructures()
        self.removeFromGame()
        
    def _accelerateOrbit(self, task):
        self.orbital_angle = self.orbital_angle + self.orbital_velocity
        self.orbital_angle = math.fmod(self.orbital_angle, 2.0*math.pi);
        self.point_path.setPos(self.orbital_radius * math.cos(self.orbital_angle),
                               self.orbital_radius * math.sin(self.orbital_angle), 0)
        self.position = self.point_path.getPos()
        self.orbital_velocity = self.orbital_velocity + 0.0001
        if self.orbital_velocity > self.max_orbital_velocity:
            self.orbit_task = taskMgr.add(self._stepOrbit, 'stepOrbit')
            return task.done
        else:
            return task.cont
    
    def _stepOrbit(self, task):
        self.orbital_angle = self.orbital_angle + self.orbital_velocity
        self.orbital_angle = math.fmod(self.orbital_angle, 2.0*math.pi)
        self.point_path.setPos(self.orbital_radius * math.cos(self.orbital_angle),
                               self.orbital_radius * math.sin(self.orbital_angle), 0)
        self.position = self.point_path.getPos()
        return task.cont
    
    def _collapseOrbit(self, task):
        self.orbital_radius = self.orbital_radius - 0.23
        self.orbital_angle = self.orbital_angle + self.orbital_velocity
        self.orbital_angle = math.fmod(self.orbital_angle, 2.0*math.pi)
        self.point_path.setPos(self.orbital_radius * math.cos(self.orbital_angle),
                               self.orbital_radius * math.sin(self.orbital_angle), 0)
        self.position = self.point_path.getPos()
#        if self.orbital_velocity < 0.01:
        if self.orbital_radius != 0:
            self.orbital_velocity = self.orbital_velocity + 0.01/(self.orbital_radius**2)
        try:
            self.orbit_path.setScale(self.orbital_radius)
        except AttributeError:
            pass # no orbit on this planet (has not been activated)
        
        if self.orbital_radius <= 0:
            self._consume()
            return task.done
        else:
            return task.cont
        
    
    def drawLines(self): 
        # put some lighting on the line
        # for some reason the ambient and directional light in the environment drain out
        # all other colors in the scene
        # this is a temporary solution just so we can view the lines... the light can be removed and
        #a glow effect will be added later
#        alight = AmbientLight('alight')
#        alnp = render.attachNewNode(alight)
#        alight.setColor(Vec4(0.2, 0.2, 0.2, 1))
#        render.setLight(alnp)
        self.lines.reset()
        self.lines.drawLines([((0,0, 0),
                               (self.point_path.getX(), self.point_path.getY(), 0))])
        self.lines.create()
    
    def drawConnections(self, task):
#        cur_pos = self.point_path.getPos()
#        paired_pos = pairedPlanetDraw.point_path.getPos()
#        paired_pos = self.point_path.getRelativePoint(pairedPlanetDraw.point_path, pairedPlanetDraw.point_path.getPos())
#        print pairedPlanetDraw.point_path.getX(), pairedPlanetDraw.point_path.getY(), pairedPlanetDraw.point_path.getZ()
        self.connections.reset()
        if(self.next_planet):
            point_list = []
            point_list.append((self.point_path.getPos(), self.next_planet.point_path.getPos()))
            self.connections.drawLines(point_list)
            self.connections.create()
        return task.cont
        
    def changePlayer(self, player):
        '''
        Change the control of the planet from the self.player to the parameter player
        @param player: Player, the player who has captured the planet by swarms
        @precondition: the player must have used the capture ability of a swarm type unit on the planet
        '''
        '''TODO: Use makeArc to change the color of the orbit'''
        
        ''' stop any constructions on the planet '''
        if(self.task_structure_timer != None):
            taskMgr.remove(self.task_structure_timer)
            self.task_structure_timer = None
        if(self.task_unit_timer != None):
            taskMgr.remove(self.task_unit_timer)
            self.task_unit_timer = None
        if(self.task_structure_timers != None):
            taskMgr.remove(self.task_structure_timers)
            self.task_structure_timers = None
        if(self.task_unit_timers != None):
            taskMgr.remove(self.task_unit_timers)
            self.task_unit_timers = None
        ''' remove previous player's control from the planet '''
        for structure in self.structures():
            self.player.structures.remove(structure)
        ''' set control of the planet to the new player '''
        for structure in self.structures():
            player.structures.append(structure)
        ''' update the construction panel for the human player'''
        from gameModel.ai import AI
        ''' if human player is the previous owner '''
        if(type(self.player) != AI):
            from gameEngine.gameEngine import updateGUI
            updateGUI.refreshUnitsAndConstructions(self)
        ''' give total control to new player '''
        self.player = player
        ''' if human player is the new owner '''
        if(type(player) != AI):
            from gameEngine.gameEngine import updateGUI
            updateGUI.refreshUnitsAndConstructions(self)
        
    def addOrbitingUnit(self, unit):
        ''' 
        Add a unit to the hosted units by the planet
        @param unit: Orbiting unit object
        '''
        self._orbiting_units.append(unit)
    
    def addOrbitingUnits(self, units):
        '''
        Add a list of units to be hosted by the planet
        @param units: iterable of units
        '''
        self._orbiting_units.extend(units)
    
    def removeOrbitingUnit(self, unit):
        ''' 
        Remove the hosted unit from the planet
        @param unit: Orbiting unit object
        '''
        self._orbiting_units.remove(unit)
    
    def removeOrbitingUnits(self, units):
        ''' 
        Remove many hosted units from the planet
        @param units: List of unit objects
        '''
        self._orbiting_units[:] = [u for u in self._orbiting_units if u not in units]
        
    def units(self):
        '''
        Generator that iterates over the hosted units.
        '''
        for unit in self._orbiting_units:
            yield unit
            
    def unitsOfPlayer(self, player):
        '''
        Generator that iterates over the hosted units belonging to the player.
        @param player, the owner of the units 
        '''
        for unit in self._orbiting_units:
            if unit.player == player:
                yield unit
    
    def unitsOfEnemy(self, player):
        '''
        Generator that iterates over the hosted units not belonging to the player.
        @param player, the owner of the units 
        '''
        for unit in self._orbiting_units:
            if unit.player != player:
                yield unit
    
    def unitsOfEnemyLowestEnergy(self, player):
        '''
        Generator that iterates over the hosted units not belonging to the player
        sorted by lowest energy.
        @param player, the owner of the units 
        '''
        energyList = sorted(self._orbiting_units, key=lambda unit: unit.energy)
        for unit in energyList:
            if unit.player != player:
                yield unit
                
    def getNumberOfUnits(self):
        '''
        Returns the number of hosted units from the planet
        '''
        return len(self._orbiting_units)
    
    def getNumberOfUnitsOfPlayer(self, player):
        '''
        Returns the number of hosted units from the planet that belongs to the player
        @param player, the owner of the units
        '''
        num = 0
        for unit in self._orbiting_units:
            if(unit.player == player):
                num = num + 1
        return num
    
    def _task_unit_timers(self):
        '''
        Generator that iterates over the hosted units construction tasks.
        '''
        for task_unit in self.task_unit_timers:
            yield task_unit
            
    def _task_structure_timers(self):
        '''
        Generator that iterates over the surface structure construction tasks.
        '''
        for task_structure in self.task_structure_timers:
            yield task_structure
            
    def _consumeUnits(self):
        if(self.task_unit_timer!=None):
            taskMgr.remove(self.task_unit_timer)
            self.task_unit_timer = None
            
        for task in self.task_unit_timers:
            taskMgr.remove(task)
        del self.task_unit_timers[:]

        from gameModel.ai import AI        
        for unit in self._orbiting_units:
            unit.player.units.remove(unit)
            if(type(unit.player) != AI):
                try:
                    unit.player.selected_units.remove(unit)
                    print "selected unit"
                except ValueError:
                    pass
            unit.removeFromGame()    
        del self._orbiting_units[:]
        
    def _consumeStructures(self):
        if(self.task_structure_timer!=None):
            taskMgr.remove(self.task_structure_timer)
            self.task_structure_timer = None
        for task in self.task_structure_timers:
            taskMgr.remove(task)
        del self.task_structure_timers[:]

        for structure in self._surface_structures:
            self.player.structures.remove(structure)
        del self._surface_structures[:]
       
    def addSurfaceStructure(self, structure):
        ''' 
        Add a structure to the surface structures by the planet
        @param structure: Surface structure object
        '''
        if(len(self._surface_structures) < MAX_NUMBER_OF_STRUCTURE):
            self._surface_structures.append(structure)
    
    def addSurfaceStructures(self, structures):
        '''
        Add a list of structures to be hosted by the planet
        @param structures: iterable of structure
        '''
        if(len(self._surface_structures) + len(structures) <= MAX_NUMBER_OF_STRUCTURE):
            self._surface_structures.extend(structures)
    
    def removeSurfaceStructure(self, structure):
        ''' 
        Remove the surface structure from the planet
        @param structure: Surface structure object
        '''
        self._surface_structures.remove(structure)
    
    def removeSurfaceStructures(self, structures):
        ''' 
        Remove many surface structures from the planet
        @param structures: List of structure objects
        '''
        self._surface_structures[:] = [s for s in self._surface_structures if s not in structures]
        
    def structures(self):
        '''
        Generator that iterates over the surface structures.
        '''
        for structure in self._surface_structures:
            yield structure
            
    def getNumberOfStructures(self):
        '''
        Returns the number of surface structures from the planet
        '''
        return len(self._surface_structures)
        
    def hasStructure(self, structure):
        '''
        Returns true if a type of the structure already exists on the planet
        @structure: String, the desired structure type
        '''
        from structures import Forge, Nexus, Extractor, Phylon, GeneratorCore,\
            PlanetaryDefenseI, PlanetaryDefenseII, PlanetaryDefenseIII, PlanetaryDefenseIV
        for surface_structure in self._surface_structures:
            if(structure == "forge" and type(surface_structure) == Forge):
                return True
            elif(structure == "nexus" and type(surface_structure) == Nexus):
                return True
            elif(structure == "extractor" and type(surface_structure) == Extractor):
                return True
            elif(structure == "phylon" and type(surface_structure) == Phylon):
                return True
            elif(structure == "generatorCore" and type(surface_structure) == GeneratorCore):
                return True
            elif(structure == "pd1" and type(surface_structure) == PlanetaryDefenseI):
                return True
            elif(structure == "pd2" and type(surface_structure) == PlanetaryDefenseII):
                return True
            elif(structure == "pd3" and type(surface_structure) == PlanetaryDefenseIII):
                return True
            elif(structure == "pd4" and type(surface_structure) == PlanetaryDefenseIV):
                return True
        return False
    
    def removeFromGame(self):
        if(self.next_planet != None):
            self.next_planet.prev_planet = None
        self.point_path.removeNode()
        from player import Player
        if type(self.player) == Player and self.player.selected_planet == self:
            self.player.selected_planet = None
        if(self.player != None):
            self.player.planets.remove(self)
        self.parent_star.removePlanet(self)