Ejemplo n.º 1
0
class PandaController(object):

    DEFAULT_FULLSCREEN = False
    DEFAULT_WIDTH = 800
    DEFAULT_HEIGHT = 600
    DEFAULT_FPS = 60
    DEFAULT_FRAME_METER = False
    DEFAULT_MUSIC_VOLUME = .3
    DEFAULT_SOUND_VOLUME = .1
    DEFAULT_MAX_DELTA = 1. / 20.
    DEFAULT_SHADERS = True

    def __init__(self):
        super(PandaController, self).__init__()
        self._timer = Timer()
        self._timer.max_delta = self.DEFAULT_MAX_DELTA
        self._tasks = task.TaskGroup()
        self._tasks.add(self._panda_task)
        self._music = None
        self._mouse_task = None
        self._relative_mouse = False

    @property
    def timer(self):
        return self._timer

    @property
    def tasks(self):
        return self._tasks

    def start(self, title):
        cfg = GlobalConf().child('panda')

        self.set_defaults(cfg)
        self.base = ShowBase()
        self.base.disableMouse()
        self.audio = self.base.sfxManagerList[0]
        self.audio3d = Audio3DManager(self.audio, camera)
        self.audio3d.setListenerVelocityAuto()
        self.audio3d.setDropOffFactor(0.1)  # HACK

        self.create_properties(title)
        self.update_properties(cfg)
        self.listen_conf(cfg)

        loadPrcFileData("", "interpolate-frames 1")
        loadPrcFileData("", "support-threads #f")
        path = getModelPath()
        path.prependPath('./data')

        self.base.enableParticles()

    def loop(self):
        self._timer.reset()
        self._timer.loop(self._loop_fn)

    def _loop_fn(self, timer):
        task_count = 1  # _panda_task
        if self._relative_mouse:
            task_count += 1  # _mouse_task
        if self._tasks.count > task_count:
            return self._tasks.update(timer)
        return False

    def set_defaults(self, cfg):
        cfg.child('fps').default(self.DEFAULT_FPS)
        cfg.child('width').default(self.DEFAULT_WIDTH)
        cfg.child('height').default(self.DEFAULT_HEIGHT)
        cfg.child('fullscreen').default(self.DEFAULT_FULLSCREEN)
        cfg.child('frame-meter').default(self.DEFAULT_FRAME_METER)
        cfg.child('music-volume').default(self.DEFAULT_MUSIC_VOLUME)
        cfg.child('sound-volume').default(self.DEFAULT_SOUND_VOLUME)

    def listen_conf(self, cfg):
        cfg.on_conf_nudge += self.update_properties

        cfg.child('fps').on_conf_change += self.update_fps
        cfg.child('frame-meter').on_conf_change += self.update_frame_meter
        cfg.child('music-volume').on_conf_change += self.update_music_volume
        cfg.child('sound-volume').on_conf_change += self.update_sound_volume

        self.audio.setVolume(cfg.child('sound-volume').value)

    def create_properties(self, title):
        self._prop = WindowProperties()
        self._prop.setTitle(title)

    def relative_mouse(self):
        if not self._relative_mouse:
            self._prop.setCursorHidden(True)
            self._prop.setMouseMode(WindowProperties.MRelative)
            self.base.win.requestProperties(self._prop)
            self._mouse_task = self._tasks.add(MouseTask())
            self._mouse_task.on_mouse_move += lambda x, y: \
                messenger.send ('mouse-move', [(x, y)])
            self._relative_mouse = True

    def absolute_mouse(self):
        if self._relative_mouse:
            self._relative_mouse = False
            if self._mouse_task:
                self._mouse_task.kill()
            self._prop.setCursorHidden(False)
            self._prop.setMouseMode(WindowProperties.MAbsolute)
            self.base.win.requestProperties(self._prop)

    def has_shaders(self):
        return self.base.win.getGsg().getSupportsBasicShaders() == 0

    def update_properties(self, cfg):
        self._prop.setSize(cfg.child('width').value, cfg.child('height').value)
        self._prop.setFullscreen(cfg.child('fullscreen').value)
        self.base.win.requestProperties(self._prop)

        self._timer.fps = cfg.child('fps').value
        self.base.setFrameRateMeter(cfg.child('frame-meter').value)

    def update_frame_meter(self, cfg):
        self.base.setFrameRateMeter(cfg.value)

    def update_fps(self, cfg):
        self._timer.fps = cfg.value

    def update_music_volume(self, cfg):
        if self._music:
            self._music.setVolume(cfg.value)

    def update_sound_volume(self, cfg):
        if self.audio:
            self.audio.setVolume(cfg.value)

    def _panda_task(self, timer):
        taskMgr.step()
        return task.running

    def set_background_color(self, *color):
        base.setBackgroundColor(*color)

    def loop_music(self, file):
        if self._music:
            self._music.setLoop(False)
            self.tasks.add(
                task.sequence(
                    task.linear(self._music.setVolume, self._music.getVolume(),
                                0.0)))

        volume = GlobalConf().path('panda.music-volume').value
        self._music = loader.loadSfx(file)
        self._music.setLoop(True)
        self.tasks.add(
            task.sequence(
                task.linear(self._music.setVolume, 0.0, volume, init=True)))
        self._music.play()
Ejemplo n.º 2
0
class PandaController (object):

    DEFAULT_FULLSCREEN = False
    DEFAULT_WIDTH = 800
    DEFAULT_HEIGHT = 600
    DEFAULT_FPS = 60
    DEFAULT_FRAME_METER = False
    DEFAULT_MUSIC_VOLUME = .3
    DEFAULT_SOUND_VOLUME = .1
    DEFAULT_MAX_DELTA = 1. / 20.
    DEFAULT_SHADERS = True
    
    def __init__ (self):
        super (PandaController, self).__init__ ()
        self._timer = Timer ()
        self._timer.max_delta = self.DEFAULT_MAX_DELTA
        self._tasks = task.TaskGroup ()
        self._tasks.add (self._panda_task)
        self._music = None
        self._mouse_task = None
        self._relative_mouse = False
        
    @property
    def timer (self):
        return self._timer

    @property
    def tasks (self):
        return self._tasks

    def start (self, title):
        cfg = GlobalConf ().child ('panda') 

        self.set_defaults (cfg)
        self.base = ShowBase ()
        self.base.disableMouse ()
        self.audio = self.base.sfxManagerList [0]
        self.audio3d = Audio3DManager (self.audio, camera)
        self.audio3d.setListenerVelocityAuto ()
        self.audio3d.setDropOffFactor (0.1) # HACK
        
        self.create_properties (title)
        self.update_properties (cfg)
        self.listen_conf (cfg)

        loadPrcFileData ("", "interpolate-frames 1")
        loadPrcFileData ("", "support-threads #f")
        path = getModelPath ()
        path.prependPath ('./data')
                
        self.base.enableParticles ()
        
    def loop (self):        
        self._timer.reset ()
        self._timer.loop (self._loop_fn)

    def _loop_fn (self, timer):
        task_count = 1               # _panda_task
        if self._relative_mouse:
            task_count += 1          # _mouse_task
        if self._tasks.count > task_count:
            return self._tasks.update (timer)
        return False
    
    def set_defaults (self, cfg):
        cfg.child ('fps').default (self.DEFAULT_FPS)
        cfg.child ('width').default (self.DEFAULT_WIDTH)
        cfg.child ('height').default (self.DEFAULT_HEIGHT)
        cfg.child ('fullscreen').default (self.DEFAULT_FULLSCREEN)
        cfg.child ('frame-meter').default (self.DEFAULT_FRAME_METER)
        cfg.child ('music-volume').default (self.DEFAULT_MUSIC_VOLUME)
        cfg.child ('sound-volume').default (self.DEFAULT_SOUND_VOLUME)
                
    def listen_conf (self, cfg):
        cfg.on_conf_nudge += self.update_properties
        
        cfg.child ('fps').on_conf_change += self.update_fps
        cfg.child ('frame-meter').on_conf_change += self.update_frame_meter
        cfg.child ('music-volume').on_conf_change += self.update_music_volume
        cfg.child ('sound-volume').on_conf_change += self.update_sound_volume

        self.audio.setVolume (cfg.child ('sound-volume').value)
    
    def create_properties (self, title):
        self._prop = WindowProperties ()
        self._prop.setTitle (title)
        
    def relative_mouse (self):
        if not self._relative_mouse:
            self._prop.setCursorHidden (True)
            self._prop.setMouseMode (WindowProperties.MRelative)
            self.base.win.requestProperties (self._prop)
            self._mouse_task = self._tasks.add (MouseTask ())
            self._mouse_task.on_mouse_move += lambda x, y: \
                messenger.send ('mouse-move', [(x, y)])
            self._relative_mouse = True
        
    def absolute_mouse (self):
        if self._relative_mouse:
            self._relative_mouse = False
            if self._mouse_task:
                self._mouse_task.kill ()
            self._prop.setCursorHidden (False)
            self._prop.setMouseMode (WindowProperties.MAbsolute)
            self.base.win.requestProperties (self._prop)
    
    def has_shaders (self):
        return self.base.win.getGsg().getSupportsBasicShaders() == 0
        
    def update_properties (self, cfg):
        self._prop.setSize (cfg.child ('width').value,
                            cfg.child ('height').value)
        self._prop.setFullscreen (cfg.child ('fullscreen').value)
        self.base.win.requestProperties (self._prop)
        
        self._timer.fps = cfg.child ('fps').value
        self.base.setFrameRateMeter (cfg.child ('frame-meter').value)

    def update_frame_meter (self, cfg):
        self.base.setFrameRateMeter (cfg.value)

    def update_fps (self, cfg):
        self._timer.fps = cfg.value

    def update_music_volume (self, cfg):
        if self._music:
            self._music.setVolume (cfg.value)

    def update_sound_volume (self, cfg):
        if self.audio:
            self.audio.setVolume (cfg.value)

    def _panda_task (self, timer):
        taskMgr.step ()
        return task.running

    def set_background_color (self, *color):
        base.setBackgroundColor (*color)
    
    def loop_music (self, file):
        if self._music:
            self._music.setLoop (False)
            self.tasks.add (task.sequence (
                task.linear (self._music.setVolume,
                             self._music.getVolume (), 0.0)))

        volume = GlobalConf ().path ('panda.music-volume').value 
        self._music = loader.loadSfx (file)
        self._music.setLoop (True)
        self.tasks.add (task.sequence (
            task.linear (self._music.setVolume, 0.0, volume, init = True)))
        self._music.play ()
Ejemplo n.º 3
0
class Fountain(object):

    def __init__(self):
        self.base = ShowBase()
        self.thrust = 0.5
        self.wind = 0.2

        self.UP = Vec3(0, 0, 1)  # might as well just make this a variable
        # set up camera
        self.base.disableMouse()
        self.base.camera.setPos(20, -20, 5)
        self.base.camera.lookAt(0, 0, 5)

        # Set up the collision traverser.  If we bind it to base.cTrav, then Panda will handle
        # management of this traverser (for example, by calling traverse() automatically for us once per frame)
        self.base.cTrav = CollisionTraverser()

        # Now let's set up some collision bits for our masks
        self.ground_bit = 1
        self.ball_bit = 2

        self.base.setBackgroundColor(0.64, 0, 0)
        # First, we build a card to represent the ground
        cm = CardMaker('ground-card')
        cm.setFrame(-60, 60, -60, 60)
        card = self.base.render.attachNewNode(cm.generate())
        card.lookAt(0, 0, -1)  # align upright
        #tex = loader.loadTexture('maps/envir-ground.jpg')
        tex = loader.loadTexture('models/textures/rock12.bmp')
        card.setTexture(tex)

        # Then we build a collisionNode which has a plane solid which will be the ground's collision
        # representation
        groundColNode = card.attachNewNode(CollisionNode('ground-cnode'))
        groundColPlane = CollisionPlane(Plane(Vec3(0, -1, 0), Point3(0, 0, 0)))
        groundColNode.node().addSolid(groundColPlane)

        # Now, set the ground to the ground mask
        groundColNode.setCollideMask(BitMask32().bit(self.ground_bit))

        # Why aren't we adding a collider?  There is no need to tell the collision traverser about this
        # collisionNode, as it will automatically be an Into object during traversal.

        # enable forces
        self.base.enableParticles()
        node = NodePath("PhysicsNode")
        node.reparentTo(self.base.render)

        # may want to have force dependent on mass eventually,
        # but at the moment assume all balls same weight
        self.force_mag = 200

        # gravity
        gravity_fn = ForceNode('world-forces')
        gravity_fnp = self.base.render.attachNewNode(gravity_fn)
        gravity_force = LinearVectorForce(0.0, 0.0, -9.81)
        gravity_fn.addForce(gravity_force)
        self.base.physicsMgr.addLinearForce(gravity_force)

        # wind
        wind_fn = ForceNode('world-forces')
        wind_fnp = self.base.render.attachNewNode(wind_fn)
        wind_force = LinearVectorForce(1.0, 0.5, 0.0)
        wind_fn.addForce(wind_force)
        self.base.physicsMgr.addLinearForce(wind_force)

        # spurt out of fountain, bounce
        self.spurt = ForceNode("spurt")
        spurt_np = self.base.render.attachNewNode(self.spurt)

        # create a list for our ball actors, not sure if I need this, but seems likely
        self.ball_actors = []

        # make a teapot
        teapot = loader.loadModel('teapot.egg')
        tex = loader.loadTexture('maps/color-grid.rgb')
        #teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
        teapot.setTexture(tex)
        teapot.reparentTo(self.base.render)
        teapot.setPos(-5, 10, 10)
        # create the first ball:
        #ball = self.create_a_ball()
        #self.enliven_ball(ball)

        smiley = loader.loadModel('smiley.egg')
        lerper = NodePath('lerper')
        smiley.setTexProjector(TextureStage.getDefault(), NodePath(), lerper)
        smiley.reparentTo(self.base.render)
        smiley.setPos(5, 10, 10)
        i = lerper.posInterval(5, VBase3(0, 1, 0))
        i.loop()

        # Tell the messenger system we're listening for smiley-into-ground messages and invoke our callback
        self.base.accept('ball_cnode-into-ground-cnode', self.ground_callback)

        ball_fountain = taskMgr.doMethodLater(.5, self.spurt_balls, 'tickTask')

        # tasks
        #self.frameTask = self.base.taskMgr.add(self.frame_loop, "frame_loop")
        #self.frameTask.last = 0

    def frame_loop(self, task):
        # Make more balls!
        dt = task.time - task.last
        task.last = task.time
        # self.move_ball(dt)
        return task.cont

    # This task increments itself so that the delay between task executions
    # gradually increases over time. If you do not change task.delayTime
    # the task will simply repeat itself every 2 seconds
    def spurt_balls(self, task):
        print "Delay:", task.delayTime
        print "Frame:", task.frame
        # task.delayTime += 1
        # for i in range(5):
        ball = self.create_a_ball()
        self.enliven_ball(ball)
        return task.again

    def move_ball(self, dt):
        pass
        #x, y, z = self.ball.getPos()
        #print x, y, z
        #print self.actor_node.getPhysicsObject().getPosition()
        # rotate ball
        #prevRot = LRotationf(self.ball.getQuat())
        #axis = self.UP.cross(self.ballV)
        #newRot = LRotationf(axis, 45, 5 * dt)
        #self.ball.setQuat(prevRot + newRot)
        #print x, y, z
        #z += dt * self.thrust
        #x += dt * self.wind
        #y += dt * self.wind
        #self.ball.setPos(x, y, z)

    def remove_force(self, actor, force, task):
        print('remove force', force)
        actor.getPhysical(0).removeLinearForce(force)
        self.spurt.removeForce(force)
        return task.done

    def apply_spurt(self, actor_node, force=None):
        print 'spurt'
        print('force', force)
        #print actor_node
        # really want this to remember its previous state with that
        # particular ball, and reduce the force from then by some amount.
        if force is None:
            # force = LinearVectorForce(0.0, 0.0, self.force_mag)
            force = LinearVectorForce(0.0, 0.0, self.force_mag)
            self.spurt.addForce(force)
            print('new', force)
            #self.force_mag -= 15
        else:
            force.getAmplitude()
        # apply the spurt
        actor_node.getPhysical(0).addLinearForce(force)
        print('added force', self.spurt.getForce(0).getVector(actor_node.getPhysicsObject())[2])
        #print force, actor_node
        #for child in self.base.render.getChildren():
        #    print child
        # set a task that will clear this force a short moment later
        self.base.taskMgr.doMethodLater(0.01, self.remove_force, 'removeForceTask',
                                        extraArgs=[actor_node, force],
                                        appendTask=True)
        print 'set do method later'

    def ground_callback(self, entry):
        '''This is our ground collision message handler.  It is called whenever a collision message is
        triggered'''
        print 'callback'
        # Get our parent actor_node
        ball_actor_node = entry.getFromNodePath().getParent().node()
        # Why do we call getParent?  Because we are passed the CollisionNode during the event and the
        # ActorNode is one level up from there.  Our node graph looks like so:
        # - ActorNode
        #   + ModelNode
        #   + CollisionNode
        # add bounce!
        self.apply_spurt(ball_actor_node)

    def create_a_ball(self):
        print 'new ball'
        ball = self.base.loader.loadModel('smiley')
        texture = self.base.loader.loadTexture('models/textures/rock12.bmp')
        #ball.setTexture(texture)
        #tex = loader.loadTexture('maps/noise.rgb')
        ball.setPos(0, 0, 0)
        ball.reparentTo(self.base.render)
        ball.setTexture(texture, 1)
        return ball

    def enliven_ball(self, ball):
        # create an actor node for the balls
        ball_actor = self.base.render.attachNewNode(ActorNode("ball_actor_node"))
        # choose a random color
        #ball_actor.setColor(random.random(), random.random(), random.random(), random.random())
        self.create_ball_coll(ball_actor)
        # apply forces to it
        self.base.physicsMgr.attachPhysicalNode(ball_actor.node())
        #spurt_force = LinearVectorForce(0.0, 0.0, self.force_mag)
        #spurt_force.setMassDependent(True)
        #self.spurt.addForce(spurt_force)
        #self.spurt.addForce()
        self.apply_spurt(ball_actor.node())
        ball.reparentTo(ball_actor)
        self.ball_actors.append(ball_actor)

    def create_ball_coll(self, ball_actor):
        # Build a collisionNode for this smiley which is a sphere of the same diameter as the model
        ball_coll_node = ball_actor.attachNewNode(CollisionNode('ball_cnode'))
        ball_coll_sphere = CollisionSphere(0, 0, 0, 1)
        ball_coll_node.node().addSolid(ball_coll_sphere)
        # Watch for collisions with our brothers, so we'll push out of each other
        ball_coll_node.node().setIntoCollideMask(BitMask32().bit(self.ball_bit))
        # we're only interested in colliding with the ground and other smileys
        cMask = BitMask32()
        cMask.setBit(self.ground_bit)
        cMask.setBit(self.ball_bit)
        ball_coll_node.node().setFromCollideMask(cMask)

        # Now, to keep the spheres out of the ground plane and each other, let's attach a physics handler to them
        ball_handler = PhysicsCollisionHandler()

        # Set the physics handler to manipulate the smiley actor's transform.
        ball_handler.addCollider(ball_coll_node, ball_actor)

        # This call adds the physics handler to the traverser list
        # (not related to last call to addCollider!)
        self.base.cTrav.addCollider(ball_coll_node, ball_handler)

        # Now, let's set the collision handler so that it will also do a CollisionHandlerEvent callback
        # But...wait?  Aren't we using a PhysicsCollisionHandler?
        # The reason why we can get away with this is that all CollisionHandlerXs are inherited from
        # CollisionHandlerEvent,
        # so all the pattern-matching event handling works, too
        ball_handler.addInPattern('%fn-into-%in')

        return ball_coll_node
Ejemplo n.º 4
0
from pandac.PandaModules import *
loadPrcFile('/c/Users/Brian/Documents/panda3d/Panda3D-CI/etc/Config.prc')
loadPrcFileData('', 'default-model-extension .egg')
from direct.showbase.ShowBase import ShowBase
base = ShowBase()
from direct.distributed.ClientRepository import ClientRepository
from lib.coginvasion.suit import Suit
base.enableParticles()
from lib.coginvasion.suit.CogStation import CogStation
from lib.coginvasion.dna.DNAParser import *
import __builtin__


class game:
    process = 'client'


__builtin__.game = game()
from lib.coginvasion.toon import ParticleLoader, Toon
from lib.coginvasion.hood.DistributedGagShop import DistributedGagShop
from direct.gui.DirectGui import *
from direct.interval.IntervalGlobal import *
from lib.coginvasion.globals import CIGlobals

base.cr = ClientRepository(['astron/direct.dc'])
base.cr.isShowingPlayerIds = False
base.cTrav = CollisionTraverser()

vfs = VirtualFileSystem.getGlobalPtr()
vfs.mount(Filename("phase_0.mf"), ".", VirtualFileSystem.MFReadOnly)
vfs.mount(Filename("phase_3.mf"), ".", VirtualFileSystem.MFReadOnly)
Ejemplo n.º 5
0
class Fountain(object):
    def __init__(self):
        self.base = ShowBase()
        self.thrust = 0.5
        self.wind = 0.2

        self.UP = Vec3(0, 0, 1)  # might as well just make this a variable
        # set up camera
        self.base.disableMouse()
        self.base.camera.setPos(20, -20, 5)
        self.base.camera.lookAt(0, 0, 5)

        # Set up the collision traverser.  If we bind it to base.cTrav, then Panda will handle
        # management of this traverser (for example, by calling traverse() automatically for us once per frame)
        self.base.cTrav = CollisionTraverser()

        # Now let's set up some collision bits for our masks
        self.ground_bit = 1
        self.ball_bit = 2

        self.base.setBackgroundColor(0.64, 0, 0)
        # First, we build a card to represent the ground
        cm = CardMaker('ground-card')
        cm.setFrame(-60, 60, -60, 60)
        card = self.base.render.attachNewNode(cm.generate())
        card.lookAt(0, 0, -1)  # align upright
        #tex = loader.loadTexture('maps/envir-ground.jpg')
        tex = loader.loadTexture('models/textures/rock12.bmp')
        card.setTexture(tex)

        # Then we build a collisionNode which has a plane solid which will be the ground's collision
        # representation
        groundColNode = card.attachNewNode(CollisionNode('ground-cnode'))
        groundColPlane = CollisionPlane(Plane(Vec3(0, -1, 0), Point3(0, 0, 0)))
        groundColNode.node().addSolid(groundColPlane)

        # Now, set the ground to the ground mask
        groundColNode.setCollideMask(BitMask32().bit(self.ground_bit))

        # Why aren't we adding a collider?  There is no need to tell the collision traverser about this
        # collisionNode, as it will automatically be an Into object during traversal.

        # enable forces
        self.base.enableParticles()
        node = NodePath("PhysicsNode")
        node.reparentTo(self.base.render)

        # may want to have force dependent on mass eventually,
        # but at the moment assume all balls same weight
        self.force_mag = 200

        # gravity
        gravity_fn = ForceNode('world-forces')
        gravity_fnp = self.base.render.attachNewNode(gravity_fn)
        gravity_force = LinearVectorForce(0.0, 0.0, -9.81)
        gravity_fn.addForce(gravity_force)
        self.base.physicsMgr.addLinearForce(gravity_force)

        # wind
        wind_fn = ForceNode('world-forces')
        wind_fnp = self.base.render.attachNewNode(wind_fn)
        wind_force = LinearVectorForce(1.0, 0.5, 0.0)
        wind_fn.addForce(wind_force)
        self.base.physicsMgr.addLinearForce(wind_force)

        # spurt out of fountain, bounce
        self.spurt = ForceNode("spurt")
        spurt_np = self.base.render.attachNewNode(self.spurt)

        # create a list for our ball actors, not sure if I need this, but seems likely
        self.ball_actors = []

        # make a teapot
        teapot = loader.loadModel('teapot.egg')
        tex = loader.loadTexture('maps/color-grid.rgb')
        #teapot.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition)
        teapot.setTexture(tex)
        teapot.reparentTo(self.base.render)
        teapot.setPos(-5, 10, 10)
        # create the first ball:
        #ball = self.create_a_ball()
        #self.enliven_ball(ball)

        smiley = loader.loadModel('smiley.egg')
        lerper = NodePath('lerper')
        smiley.setTexProjector(TextureStage.getDefault(), NodePath(), lerper)
        smiley.reparentTo(self.base.render)
        smiley.setPos(5, 10, 10)
        i = lerper.posInterval(5, VBase3(0, 1, 0))
        i.loop()

        # Tell the messenger system we're listening for smiley-into-ground messages and invoke our callback
        self.base.accept('ball_cnode-into-ground-cnode', self.ground_callback)

        ball_fountain = taskMgr.doMethodLater(.5, self.spurt_balls, 'tickTask')

        # tasks
        #self.frameTask = self.base.taskMgr.add(self.frame_loop, "frame_loop")
        #self.frameTask.last = 0

    def frame_loop(self, task):
        # Make more balls!
        dt = task.time - task.last
        task.last = task.time
        # self.move_ball(dt)
        return task.cont

    # This task increments itself so that the delay between task executions
    # gradually increases over time. If you do not change task.delayTime
    # the task will simply repeat itself every 2 seconds
    def spurt_balls(self, task):
        print "Delay:", task.delayTime
        print "Frame:", task.frame
        # task.delayTime += 1
        # for i in range(5):
        ball = self.create_a_ball()
        self.enliven_ball(ball)
        return task.again

    def move_ball(self, dt):
        pass
        #x, y, z = self.ball.getPos()
        #print x, y, z
        #print self.actor_node.getPhysicsObject().getPosition()
        # rotate ball
        #prevRot = LRotationf(self.ball.getQuat())
        #axis = self.UP.cross(self.ballV)
        #newRot = LRotationf(axis, 45, 5 * dt)
        #self.ball.setQuat(prevRot + newRot)
        #print x, y, z
        #z += dt * self.thrust
        #x += dt * self.wind
        #y += dt * self.wind
        #self.ball.setPos(x, y, z)

    def remove_force(self, actor, force, task):
        print('remove force', force)
        actor.getPhysical(0).removeLinearForce(force)
        self.spurt.removeForce(force)
        return task.done

    def apply_spurt(self, actor_node, force=None):
        print 'spurt'
        print('force', force)
        #print actor_node
        # really want this to remember its previous state with that
        # particular ball, and reduce the force from then by some amount.
        if force is None:
            # force = LinearVectorForce(0.0, 0.0, self.force_mag)
            force = LinearVectorForce(0.0, 0.0, self.force_mag)
            self.spurt.addForce(force)
            print('new', force)
            #self.force_mag -= 15
        else:
            force.getAmplitude()
        # apply the spurt
        actor_node.getPhysical(0).addLinearForce(force)
        print(
            'added force',
            self.spurt.getForce(0).getVector(actor_node.getPhysicsObject())[2])
        #print force, actor_node
        #for child in self.base.render.getChildren():
        #    print child
        # set a task that will clear this force a short moment later
        self.base.taskMgr.doMethodLater(0.01,
                                        self.remove_force,
                                        'removeForceTask',
                                        extraArgs=[actor_node, force],
                                        appendTask=True)
        print 'set do method later'

    def ground_callback(self, entry):
        '''This is our ground collision message handler.  It is called whenever a collision message is
        triggered'''
        print 'callback'
        # Get our parent actor_node
        ball_actor_node = entry.getFromNodePath().getParent().node()
        # Why do we call getParent?  Because we are passed the CollisionNode during the event and the
        # ActorNode is one level up from there.  Our node graph looks like so:
        # - ActorNode
        #   + ModelNode
        #   + CollisionNode
        # add bounce!
        self.apply_spurt(ball_actor_node)

    def create_a_ball(self):
        print 'new ball'
        ball = self.base.loader.loadModel('smiley')
        texture = self.base.loader.loadTexture('models/textures/rock12.bmp')
        #ball.setTexture(texture)
        #tex = loader.loadTexture('maps/noise.rgb')
        ball.setPos(0, 0, 0)
        ball.reparentTo(self.base.render)
        ball.setTexture(texture, 1)
        return ball

    def enliven_ball(self, ball):
        # create an actor node for the balls
        ball_actor = self.base.render.attachNewNode(
            ActorNode("ball_actor_node"))
        # choose a random color
        #ball_actor.setColor(random.random(), random.random(), random.random(), random.random())
        self.create_ball_coll(ball_actor)
        # apply forces to it
        self.base.physicsMgr.attachPhysicalNode(ball_actor.node())
        #spurt_force = LinearVectorForce(0.0, 0.0, self.force_mag)
        #spurt_force.setMassDependent(True)
        #self.spurt.addForce(spurt_force)
        #self.spurt.addForce()
        self.apply_spurt(ball_actor.node())
        ball.reparentTo(ball_actor)
        self.ball_actors.append(ball_actor)

    def create_ball_coll(self, ball_actor):
        # Build a collisionNode for this smiley which is a sphere of the same diameter as the model
        ball_coll_node = ball_actor.attachNewNode(CollisionNode('ball_cnode'))
        ball_coll_sphere = CollisionSphere(0, 0, 0, 1)
        ball_coll_node.node().addSolid(ball_coll_sphere)
        # Watch for collisions with our brothers, so we'll push out of each other
        ball_coll_node.node().setIntoCollideMask(BitMask32().bit(
            self.ball_bit))
        # we're only interested in colliding with the ground and other smileys
        cMask = BitMask32()
        cMask.setBit(self.ground_bit)
        cMask.setBit(self.ball_bit)
        ball_coll_node.node().setFromCollideMask(cMask)

        # Now, to keep the spheres out of the ground plane and each other, let's attach a physics handler to them
        ball_handler = PhysicsCollisionHandler()

        # Set the physics handler to manipulate the smiley actor's transform.
        ball_handler.addCollider(ball_coll_node, ball_actor)

        # This call adds the physics handler to the traverser list
        # (not related to last call to addCollider!)
        self.base.cTrav.addCollider(ball_coll_node, ball_handler)

        # Now, let's set the collision handler so that it will also do a CollisionHandlerEvent callback
        # But...wait?  Aren't we using a PhysicsCollisionHandler?
        # The reason why we can get away with this is that all CollisionHandlerXs are inherited from
        # CollisionHandlerEvent,
        # so all the pattern-matching event handling works, too
        ball_handler.addInPattern('%fn-into-%in')

        return ball_coll_node