Ejemplo n.º 1
0
class _PhysicsManagerClass:

    def __init__(self):
        from panda3d.bullet import BulletWorld
        self._world = BulletWorld()
        self._world.set_gravity(0, 0, -9.807)

        self.tick_rate = None
        self.time = 0.0

        self._rigid_bodies = {}

    def update(self):
        self._world.do_physics(1./self.tick_rate)
        self.time += 1./self.tick_rate

    def get_linear_velocity(self, entity_id):
        node = self._rigid_bodies[entity_id]
        return tuple(node.get_linear_velocity())

    def set_linear_velocity(self, entity_id, velocity):
        node = self._rigid_bodies[entity_id]
        return node.set_linear_velocity(*velocity)

    def get_angular_velocity(self, entity_id):
        node = self._rigid_bodies[entity_id]
        return tuple(node.get_angular_velocity())

    def set_angular_velocity(self, entity_id, velocity):
        node = self._rigid_bodies[entity_id]
        return node.set_angular_velocity(*velocity)

    def on_entity_created(self, entity_id, entity):
        parent = entity.get_parent()
        nodepath = parent.find("+BulletRigidBodyNode")
        self._world.attach_rigid_body(nodepath.node())

    def on_entity_destroyed(self, entity):
        parent = entity.get_parent()
        nodepath = parent.find("+BulletRigidBodyNode")
        self._world.remove_rigid_body(nodepath.node())
debug_node.showNormals(False)
debug_np = s.render.attach_new_node(debug_node)
bullet_world.set_debug_node(debug_node)
debug_np.show()


# The object in question
mass = BulletRigidBodyNode()
mass.set_mass(1)
mass.setLinearSleepThreshold(0)
mass.setAngularSleepThreshold(0)
shape = BulletSphereShape(1)
mass.add_shape(shape)
mass_node = s.render.attach_new_node(mass)
mass_node.set_hpr(1, 1, 1)
bullet_world.attach_rigid_body(mass)
model = s.loader.load_model('models/smiley')
model.reparent_to(mass_node)
model_axis = loader.load_model('models/zup-axis')
model_axis.reparent_to(model)
model_axis.set_pos(0, 0, 0)
model_axis.set_scale(0.2)


# The orientation to reach
target_node = s.loader.load_model('models/smiley')
target_node.reparent_to(s.render)
target_node.set_hpr(0,0,0)
target_node.set_scale(1.5)
target_node.set_render_mode_wireframe()
target_node.set_two_sided(True)
Ejemplo n.º 3
0
class World(object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """
    def __init__(self,
                 camera,
                 debug=False,
                 audio3d=None,
                 client=None,
                 server=None):
        self.objects = {}

        self.incarnators = []

        self.collidables = set()

        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.scene = NodePath('world')

        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)

        self.debug = debug

        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.scene.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)

    def get_incarn(self):
        return random.choice(self.incarnators)

    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.scene.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.scene)
            elif obj.solid:
                obj.node = self.scene.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj

    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)

    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break
            trash = self.garbage.pop()
            if (isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if (isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del (trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Ejemplo n.º 4
0
class PhysicsManager:

    def __init__(self, root_nodepath, world):
        self.world = BulletWorld()
        self.world.setGravity((0, 0, -9.81))

        self._timestep = 1 / world.tick_rate

        # # Seems that this does not function
        # on_contact_added = PythonCallbackObject(self._on_contact_added)
        # self.world.set_contact_added_callback(on_contact_added)
        # on_filter = PythonCallbackObject(self._filter_collision)
        # self.world.set_filter_callback(on_filter)

        self.listener = DirectObject()
        self.listener.accept('bullet-contact-added', self._on_contact_added)
        self.listener.accept('bullet-contact-destroyed', self._on_contact_removed)

        self.tracked_contacts = defaultdict(int)
        self.existing_collisions = set()

        # Debugging info
        debug_node = BulletDebugNode('Debug')
        debug_node.showWireframe(True)
        debug_node.showConstraints(True)
        debug_node.showBoundingBoxes(False)
        debug_node.showNormals(False)

        # Add to world
        self.debug_nodepath = root_nodepath.attachNewNode(debug_node)
        self.world.set_debug_node(debug_node)
        self.debug_nodepath.show()

    def _on_contact_removed(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] -= 1

    def _on_contact_added(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] += 1

    def _dispatch_collisions(self):
        # Dispatch collisions
        existing_collisions = self.existing_collisions

        for pair, contact_count in self.tracked_contacts.items():
            # If is new collision
            if contact_count > 0 and pair not in existing_collisions:
                existing_collisions.add(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = node_a.get_python_tag("entity")
                entity_b = node_b.get_python_tag("entity")

                if not (entity_a and entity_b):
                    continue

                contact_result = ContactResult(self.world, node_a, node_b)
                entity_a.messenger.send("collision_started", entity=entity_b, contacts=contact_result.contacts_a)
                entity_b.messenger.send("collision_started", entity=entity_a, contacts=contact_result.contacts_b)

            # Ended collision
            elif contact_count == 0 and pair in existing_collisions:
                existing_collisions.remove(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = node_a.get_python_tag("entity")
                entity_b = node_b.get_python_tag("entity")

                if not (entity_a and entity_b):
                    continue

                entity_a.messenger.send("collision_stopped", entity_b)
                entity_b.messenger.send("collision_stopped", entity_a)

    def add_entity(self, entity, component):
        body = component.body
        self.world.attach_rigid_body(body)
        body.set_python_tag("entity", entity)

    def remove_entity(self, entity, component):
        body = component.body
        self.world.remove_rigid_body(body)
        body.clear_python_tag("entity")

    def tick(self):
        self.world.do_physics(self._timestep)
        self._dispatch_collisions()

    def resimulate_pawn(self, pawn):
        pass
Ejemplo n.º 5
0
debug_node.showBoundingBoxes(False)
debug_node.showNormals(False)
debug_np = s.render.attach_new_node(debug_node)
bullet_world.set_debug_node(debug_node)
debug_np.show()

# The object in question
mass = BulletRigidBodyNode()
mass.set_mass(1)
mass.setLinearSleepThreshold(0)
mass.setAngularSleepThreshold(0)
shape = BulletSphereShape(1)
mass.add_shape(shape)
mass_node = s.render.attach_new_node(mass)
mass_node.set_hpr(1, 1, 1)
bullet_world.attach_rigid_body(mass)
model = s.loader.load_model('models/smiley')
model.reparent_to(mass_node)
model_axis = loader.load_model('models/zup-axis')
model_axis.reparent_to(model)
model_axis.set_pos(0, 0, 0)
model_axis.set_scale(0.2)

# The orientation to reach
target_node = s.loader.load_model('models/smiley')
target_node.reparent_to(s.render)
target_node.set_hpr(0, 0, 0)
target_node.set_scale(1.5)
target_node.set_render_mode_wireframe()
target_node.set_two_sided(True)
Ejemplo n.º 6
0
class World (object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """

    def __init__(self, camera, debug=False, audio3d=None, client=None, server=None):
        self.objects = {}

        self.incarnators = []

        self.collidables = set()

        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.scene = NodePath('world')


        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)

        self.debug = debug

        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.scene.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)


    def get_incarn(self):
        return random.choice(self.incarnators)


    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.scene.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.scene)
            elif obj.solid:
                obj.node = self.scene.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj



    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)



    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break;
            trash = self.garbage.pop()
            if(isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if(isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del(trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Ejemplo n.º 7
0
class BlockTutorial(ShowBase):
    def __init__(self):
        # Step 2: Set up graphics
        ShowBase.__init__(self)
        self.setup_camera()
        self.load_block_model()

        # Step 3: Set up physics
        self.create_physics()
        self.load_block_physics()

        # Step 4: Link graphics and physics
        self.link()

        # This lets us see the debug skeletons for the physics objects.
        self.setup_debug()

        # Setup keybindings
        print
        print "Keybindings"
        print "-----------"

        # Turn on/off the debug skeletons by pressing 'd'
        self.accept('d', self.toggle_debug)
        print "d\t: toggle debug mode"

        # Turn on/off physics simulation by pressing 'space'
        self.accept('space', self.toggle_physics)
        print "space\t: toggle physics"

        # Exit the application by pressing 'esc'
        self.accept('escape', sys.exit)
        print "esc\t: exit"

    def setup_camera(self):
        """Position the camera so we can see the objects in the scene"""

        self.cam.set_pos(-8, -6, 2.75)
        self.cam.look_at((0, 0, 0))

    def load_block_model(self):
        """Load the 3D model of a block, and tell Panda3D to render it"""

        self.block_graphics = self.loader.loadModel("wood_block.egg")
        self.block_graphics.reparent_to(self.render)
        self.block_graphics.set_scale(0.2, 0.2, 0.2)

    def create_physics(self):
        """Create the physical world, and start a task to simulate physics"""

        self.world = BulletWorld()
        self.world.set_gravity((0, 0, -9.81))

        self.physics_on = False
        self.taskMgr.add(self.step_physics, "physics")

    def step_physics(self, task):
        """Get the amount of time that has elapsed, and simulate physics for
        that amount of time"""

        if self.physics_on:
            dt = globalClock.get_dt()
            self.world.do_physics(dt)
        return task.cont

    def toggle_physics(self):
        """Turn physics on or off."""

        self.physics_on = not (self.physics_on)

    def load_block_physics(self):
        """Create collision geometry and a physical body for the block."""

        self.block_body = BulletRigidBodyNode('block-physics')
        self.block_body.add_shape(BulletBoxShape((0.2, 0.6, 0.2)))
        self.block_body.set_mass(1.0)
        self.world.attach_rigid_body(self.block_body)

    def link(self):
        """Tell Panda3D that the block's physics and graphics should be
        linked, by making the physics NodePath be the parent of the
        graphics NodePath.

        """

        self.block_physics = self.render.attach_new_node(self.block_body)
        self.block_graphics.reparent_to(self.block_physics)

    def setup_debug(self):
        """Set up a debug node, which will render skeletons for all the
        physics objects in the physics world."""

        debug_node = BulletDebugNode('Debug')
        debug_node.show_wireframe(True)
        debug_node.show_constraints(True)
        debug_node.show_bounding_boxes(True)
        debug_node.show_normals(True)
        self.world.set_debug_node(debug_node)
        self.debug_np = self.render.attach_new_node(debug_node)

    def toggle_debug(self):
        """Turn off/on rendering of the physics skeletons."""
        if self.debug_np.is_hidden():
            self.debug_np.show()
            # simulate physics for a tiny amount of time, because the
            # skeletons won't actually be rendered until physics has
            # started
            self.world.do_physics(0.0000001)
        else:
            self.debug_np.hide()
Ejemplo n.º 8
0
class BlockTutorial(ShowBase):

    def __init__(self):
        # Step 2: Set up graphics
        ShowBase.__init__(self)
        self.setup_camera()
        self.load_block_model()

        # Step 3: Set up physics
        self.create_physics()
        self.load_block_physics()

        # Step 4: Link graphics and physics
        self.link()

        # This lets us see the debug skeletons for the physics objects.
        self.setup_debug()

        # Setup keybindings
        print
        print "Keybindings"
        print "-----------"

        # Turn on/off the debug skeletons by pressing 'd'
        self.accept('d', self.toggle_debug)
        print "d\t: toggle debug mode"

        # Turn on/off physics simulation by pressing 'space'
        self.accept('space', self.toggle_physics)
        print "space\t: toggle physics"

        # Exit the application by pressing 'esc'
        self.accept('escape', sys.exit)
        print "esc\t: exit"

    def setup_camera(self):
        """Position the camera so we can see the objects in the scene"""

        self.cam.set_pos(-8, -6, 2.75)
        self.cam.look_at((0, 0, 0))

    def load_block_model(self):
        """Load the 3D model of a block, and tell Panda3D to render it"""

        self.block_graphics = self.loader.loadModel("wood_block.egg")
        self.block_graphics.reparent_to(self.render)
        self.block_graphics.set_scale(0.2, 0.2, 0.2)

    def create_physics(self):
        """Create the physical world, and start a task to simulate physics"""

        self.world = BulletWorld()
        self.world.set_gravity((0, 0, -9.81))

        self.physics_on = False
        self.taskMgr.add(self.step_physics, "physics")

    def step_physics(self, task):
        """Get the amount of time that has elapsed, and simulate physics for
        that amount of time"""

        if self.physics_on:
            dt = globalClock.get_dt()
            self.world.do_physics(dt)
        return task.cont

    def toggle_physics(self):
        """Turn physics on or off."""

        self.physics_on = not(self.physics_on)

    def load_block_physics(self):
        """Create collision geometry and a physical body for the block."""

        self.block_body = BulletRigidBodyNode('block-physics')
        self.block_body.add_shape(BulletBoxShape((0.2, 0.6, 0.2)))
        self.block_body.set_mass(1.0)
        self.world.attach_rigid_body(self.block_body)

    def link(self):
        """Tell Panda3D that the block's physics and graphics should be
        linked, by making the physics NodePath be the parent of the
        graphics NodePath.

        """

        self.block_physics = self.render.attach_new_node(self.block_body)
        self.block_graphics.reparent_to(self.block_physics)

    def setup_debug(self):
        """Set up a debug node, which will render skeletons for all the
        physics objects in the physics world."""

        debug_node = BulletDebugNode('Debug')
        debug_node.show_wireframe(True)
        debug_node.show_constraints(True)
        debug_node.show_bounding_boxes(True)
        debug_node.show_normals(True)
        self.world.set_debug_node(debug_node)
        self.debug_np = self.render.attach_new_node(debug_node)

    def toggle_debug(self):
        """Turn off/on rendering of the physics skeletons."""
        if self.debug_np.is_hidden():
            self.debug_np.show()
            # simulate physics for a tiny amount of time, because the
            # skeletons won't actually be rendered until physics has
            # started
            self.world.do_physics(0.0000001)
        else:
            self.debug_np.hide()
Ejemplo n.º 9
0
class World (object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """

    def __init__(self, camera, debug=False, audio3d=None, client=None, server=None):
        self.objects = {}
        self.incarnators = []
        self.collidables = set()
        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.render = NodePath('world')
        self.camera = camera
        self.audio3d = audio3d
        self.ambient = self._make_ambient()
        self.celestials = CompositeObject()
        self.sky = self.attach(Sky())
        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)
        self.debug = debug
        self.client = client
        self.server = server
        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.render.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)

    def _make_ambient(self):
        alight = AmbientLight('ambient')
        alight.set_color(VBase4(*DEFAULT_AMBIENT_COLOR))
        node = self.render.attach_new_node(alight)
        self.render.set_light(node)
        return node

    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.render.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.render)
            elif obj.solid:
                obj.node = self.render.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj

    def get_incarn(self):
        return random.choice(self.incarnators)

    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def set_ambient(self, color):
        """
        Sets the ambient light to the given color.
        """
        self.ambient.node().set_color(VBase4(*color))

    def add_celestial(self, azimuth, elevation, color, intensity, radius, visible):
        """
        Adds a celestial light source to the scene. If it is a visible celestial, also add a sphere model.
        """
        if not self.camera:
            return
        location = Vec3(to_cartesian(azimuth, elevation, 1000.0 * 255.0 / 256.0))
        if intensity:
            dlight = DirectionalLight('celestial')
            dlight.set_color((color[0] * intensity, color[1] * intensity,
                color[2] * intensity, 1.0))
            node = self.render.attach_new_node(dlight)
            node.look_at(*(location * -1))
            self.render.set_light(node)
        if visible:
            if radius <= 2.0:
                samples = 6
            elif radius >= 36.0:
                samples = 40
            else:
                samples = int(round(((1.5 * radius) * (2 / 3.0)) + 3.75))
            celestial = Dome(radius * 1.5, samples, 2, color, 0, location,
                ((-(math.degrees(azimuth))), 90 + math.degrees(elevation), 0))
            self.celestials.attach(celestial)

    def create_celestial_node(self):
        bounds = self.camera.node().get_lens().make_bounds()
        self.celestials = self.celestials.create_node()
        self.celestials.set_transparency(TransparencyAttrib.MAlpha)
        self.celestials.set_light_off()
        self.celestials.set_effect(CompassEffect.make(self.camera, CompassEffect.PPos))
        self.celestials.node().set_bounds(bounds)
        self.celestials.node().set_final(True)
        self.celestials.reparent_to(self.render)

    def register_collider(self, obj):
        assert isinstance(obj, PhysicalObject)
        self.collidables.add(obj)

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)

    def do_explosion(self, node, radius, force):
        center = node.get_pos(self.render);
        expl_body = BulletGhostNode("expl")
        expl_shape = BulletSphereShape(radius)
        expl_body.add_shape(expl_shape)
        expl_bodyNP = self.render.attach_new_node(expl_body)
        expl_bodyNP.set_pos(center)
        self.physics.attach_ghost(expl_body)
        result = self.physics.contact_test(expl_body)
        for contact in result.getContacts():
            n0_name = contact.getNode0().get_name()
            n1_name = contact.getNode1().get_name()
            obj = None
            try:
                obj = self.objects[n1_name]
            except:
                break
            if n0_name == "expl" and n1_name not in EXPLOSIONS_DONT_PUSH and not n1_name.startswith('Walker'):
                # repeat contact test with just this pair of objects
                # otherwise all manifold point values will be the same
                # for all objects in original result
                real_c = self.physics.contact_test_pair(expl_body, obj.solid)
                mpoint = real_c.getContacts()[0].getManifoldPoint()
                distance = mpoint.getDistance()
                if distance < 0:
                    if hasattr(obj, 'decompose'):
                        obj.decompose()
                    else:
                        expl_vec = Vec3(mpoint.getPositionWorldOnA() - mpoint.getPositionWorldOnB())
                        expl_vec.normalize()
                        magnitude = force * 1.0/math.sqrt(abs(radius - abs(distance)))
                        obj.solid.set_active(True)
                        obj.solid.apply_impulse(expl_vec*magnitude, mpoint.getLocalPointB())
                    if hasattr(obj, 'damage'):
                        obj.damage(magnitude/5)
        self.physics.remove_ghost(expl_body)
        expl_bodyNP.detach_node()
        del(expl_body, expl_bodyNP)

    def do_plasma_push(self, plasma, node, energy):
        obj = None
        try:
            obj = self.objects[node]
        except:
            raise
    
        if node not in EXPLOSIONS_DONT_PUSH and not node.startswith('Walker'):
            if hasattr(obj, 'decompose'):
                obj.decompose()
            else:
                solid = obj.solid
                dummy_node = NodePath('tmp')
                dummy_node.set_hpr(plasma.hpr)
                dummy_node.set_pos(plasma.pos)
                f_vec = render.get_relative_vector(dummy_node, Vec3(0,0,1))
                local_point = (obj.node.get_pos() - dummy_node.get_pos()) *-1
                f_vec.normalize()
                solid.set_active(True)
                try:
                    solid.apply_impulse(f_vec*(energy*35), Point3(local_point))
                except:
                    pass
                del(dummy_node)
        if hasattr(obj, 'damage'):
            obj.damage(energy*5)


    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break;
            trash = self.garbage.pop()
            if(isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if(isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del(trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Ejemplo n.º 10
0
class Environment:
    def __init__(self, app, map_name):
        map_file = 'assets/maps/{}/{}.bam'.format(map_name, map_name)
        self.app = app

        self.physics_world = BulletWorld()

        node = BulletRigidBodyNode('Ground')
        self.np = self.app.render.attach_new_node(node)
        self.np.setPos(0, 0, 0)
        self.physics_world.attachRigidBody(node)

        self.model = loader.load_model(map_file)
        self.model.reparent_to(self.np)

        self.env_data = EnvironmentData(self.model, map_name, 'maps')

        self.physics_world.setGravity(self.env_data.gravity)

        sky = self.model.find(SKYSPHERE)
        sky.reparent_to(base.cam)
        sky.set_bin('background', 0)
        sky.set_depth_write(False)
        sky.set_compass()
        sky.set_light_off()

        # Bullet collision mesh
        collision_solids = self.model.find_all_matches(
            '{}*'.format(TERRAIN_COLLIDER)
        )

        #collision_solids.hide()

        for collision_solid in collision_solids:
            collision_solid.flatten_strong()
            for geom_node in collision_solid.find_all_matches('**/+GeomNode'):
                mesh = BulletTriangleMesh()
                for geom in geom_node.node().get_geoms():
                    mesh.addGeom(geom)
                shape = BulletTriangleMeshShape(mesh, dynamic=False)
                terrain_node = BulletRigidBodyNode('terrain')
                terrain_node.add_shape(shape)
                terrain_node.set_friction(self.env_data.friction)
                terrain_np = geom_node.attach_new_node(terrain_node)
                terrain_np.set_collide_mask(CM_TERRAIN | CM_COLLIDE)
                self.physics_world.attach_rigid_body(terrain_node)

    def add_physics_node(self, node):
        self.physics_world.attach_rigid_body(node)

    def update_physics(self):
        dt = globalClock.dt
        # FIXME: Pull from settings
        min_frame_rate = 30
        max_frame_time = 1.0 / min_frame_rate
        if dt <= max_frame_time:
            self.physics_world.do_physics(dt)
        else:
            self.physics_world.do_physics(max_frame_time)

    def get_spawn_points(self):
        spawn_nodes = [
            sp
            for sp in self.np.find_all_matches("**/{}*".format(SPAWN_POINTS))
        ]
        spawn_points = {}
        for sn in spawn_nodes:
            _, _, idx = sn.name.partition(':')
            idx = int(idx)
            spawn_points[idx] = sn
        sorted_spawn_points = [
            spawn_points[key]
            for key in sorted(spawn_points.keys())
        ]
        return sorted_spawn_points
Ejemplo n.º 11
0
class World(object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """
    def __init__(self,
                 camera,
                 debug=False,
                 audio3d=None,
                 client=None,
                 server=None):
        self.objects = {}
        self.incarnators = []
        self.collidables = set()
        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.render = NodePath('world')
        self.camera = camera
        self.audio3d = audio3d
        self.ambient = self._make_ambient()
        self.celestials = CompositeObject()
        self.sky = self.attach(Sky())
        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)
        self.debug = debug
        self.client = client
        self.server = server
        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.render.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)

    def _make_ambient(self):
        alight = AmbientLight('ambient')
        alight.set_color(VBase4(*DEFAULT_AMBIENT_COLOR))
        node = self.render.attach_new_node(alight)
        self.render.set_light(node)
        return node

    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.render.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.render)
            elif obj.solid:
                obj.node = self.render.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj

    def get_incarn(self):
        return random.choice(self.incarnators)

    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def set_ambient(self, color):
        """
        Sets the ambient light to the given color.
        """
        self.ambient.node().set_color(VBase4(*color))

    def add_celestial(self, azimuth, elevation, color, intensity, radius,
                      visible):
        """
        Adds a celestial light source to the scene. If it is a visible celestial, also add a sphere model.
        """
        if not self.camera:
            return
        location = Vec3(
            to_cartesian(azimuth, elevation, 1000.0 * 255.0 / 256.0))
        if intensity:
            dlight = DirectionalLight('celestial')
            dlight.set_color((color[0] * intensity, color[1] * intensity,
                              color[2] * intensity, 1.0))
            node = self.render.attach_new_node(dlight)
            node.look_at(*(location * -1))
            self.render.set_light(node)
        if visible:
            if radius <= 2.0:
                samples = 6
            elif radius >= 36.0:
                samples = 40
            else:
                samples = int(round(((1.5 * radius) * (2 / 3.0)) + 3.75))
            celestial = Dome(
                radius * 1.5, samples, 2, color, 0, location,
                ((-(math.degrees(azimuth))), 90 + math.degrees(elevation), 0))
            self.celestials.attach(celestial)

    def create_celestial_node(self):
        bounds = self.camera.node().get_lens().make_bounds()
        self.celestials = self.celestials.create_node()
        self.celestials.set_transparency(TransparencyAttrib.MAlpha)
        self.celestials.set_light_off()
        self.celestials.set_effect(
            CompassEffect.make(self.camera, CompassEffect.PPos))
        self.celestials.node().set_bounds(bounds)
        self.celestials.node().set_final(True)
        self.celestials.reparent_to(self.render)

    def register_collider(self, obj):
        assert isinstance(obj, PhysicalObject)
        self.collidables.add(obj)

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)

    def do_explosion(self, node, radius, force):
        center = node.get_pos(self.render)
        expl_body = BulletGhostNode("expl")
        expl_shape = BulletSphereShape(radius)
        expl_body.add_shape(expl_shape)
        expl_bodyNP = self.render.attach_new_node(expl_body)
        expl_bodyNP.set_pos(center)
        self.physics.attach_ghost(expl_body)
        result = self.physics.contact_test(expl_body)
        for contact in result.getContacts():
            n0_name = contact.getNode0().get_name()
            n1_name = contact.getNode1().get_name()
            obj = None
            try:
                obj = self.objects[n1_name]
            except:
                break
            if n0_name == "expl" and n1_name not in EXPLOSIONS_DONT_PUSH and not n1_name.startswith(
                    'Walker'):
                # repeat contact test with just this pair of objects
                # otherwise all manifold point values will be the same
                # for all objects in original result
                real_c = self.physics.contact_test_pair(expl_body, obj.solid)
                mpoint = real_c.getContacts()[0].getManifoldPoint()
                distance = mpoint.getDistance()
                if distance < 0:
                    if hasattr(obj, 'decompose'):
                        obj.decompose()
                    else:
                        expl_vec = Vec3(mpoint.getPositionWorldOnA() -
                                        mpoint.getPositionWorldOnB())
                        expl_vec.normalize()
                        magnitude = force * 1.0 / math.sqrt(
                            abs(radius - abs(distance)))
                        obj.solid.set_active(True)
                        obj.solid.apply_impulse(expl_vec * magnitude,
                                                mpoint.getLocalPointB())
                    if hasattr(obj, 'damage'):
                        obj.damage(magnitude / 5)
        self.physics.remove_ghost(expl_body)
        expl_bodyNP.detach_node()
        del (expl_body, expl_bodyNP)

    def do_plasma_push(self, plasma, node, energy):
        obj = None
        try:
            obj = self.objects[node]
        except:
            raise

        if node not in EXPLOSIONS_DONT_PUSH and not node.startswith('Walker'):
            if hasattr(obj, 'decompose'):
                obj.decompose()
            else:
                solid = obj.solid
                dummy_node = NodePath('tmp')
                dummy_node.set_hpr(plasma.hpr)
                dummy_node.set_pos(plasma.pos)
                f_vec = render.get_relative_vector(dummy_node, Vec3(0, 0, 1))
                local_point = (obj.node.get_pos() - dummy_node.get_pos()) * -1
                f_vec.normalize()
                solid.set_active(True)
                try:
                    solid.apply_impulse(f_vec * (energy * 35),
                                        Point3(local_point))
                except:
                    pass
                del (dummy_node)
        if hasattr(obj, 'damage'):
            obj.damage(energy * 5)

    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break
            trash = self.garbage.pop()
            if (isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if (isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del (trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Ejemplo n.º 12
0
class app(ShowBase):
    def __init__(self):
        load_prc_file_data(
            "", """
            win-size 1920 1080
            window-title Panda3D Arena Sample FPS Bullet Auto Colliders PBR HW Skinning
            show-frame-rate-meter #t
            framebuffer-srgb #t
            framebuffer-multisample 1
            multisamples 4
            view-frustum-cull 0
            textures-power-2 none
            hardware-animated-vertices #t
            gl-depth-zero-to-one true
            clock-frame-rate 60
            interpolate-frames 1
            cursor-hidden #t
            fullscreen #f
        """)

        # Initialize the showbase
        super().__init__()
        gltf.patch_loader(self.loader)

        props = WindowProperties()
        props.set_mouse_mode(WindowProperties.M_relative)
        base.win.request_properties(props)
        base.set_background_color(0.5, 0.5, 0.8)

        self.camLens.set_fov(80)
        self.camLens.set_near_far(0.01, 90000)
        self.camLens.set_focal_length(7)
        # self.camera.set_pos(0, 0, 2)

        # ConfigVariableManager.getGlobalPtr().listVariables()

        # point light generator
        for x in range(0, 3):
            plight_1 = PointLight('plight')
            # add plight props here
            plight_1_node = self.render.attach_new_node(plight_1)
            # group the lights close to each other to create a sun effect
            plight_1_node.set_pos(random.uniform(-21, -20),
                                  random.uniform(-21, -20),
                                  random.uniform(20, 21))
            self.render.set_light(plight_1_node)

        # point light for volumetric lighting filter
        plight_1 = PointLight('plight')
        # add plight props here
        plight_1_node = self.render.attach_new_node(plight_1)
        # group the lights close to each other to create a sun effect
        plight_1_node.set_pos(random.uniform(-21, -20),
                              random.uniform(-21, -20), random.uniform(20, 21))
        self.render.set_light(plight_1_node)

        scene_filters = CommonFilters(base.win, base.cam)
        scene_filters.set_bloom()
        scene_filters.set_high_dynamic_range()
        scene_filters.set_exposure_adjust(0.6)
        scene_filters.set_gamma_adjust(1.1)
        # scene_filters.set_volumetric_lighting(plight_1_node, 32, 0.5, 0.7, 0.1)
        # scene_filters.set_blur_sharpen(0.9)
        # scene_filters.set_ambient_occlusion(32, 0.05, 2.0, 0.01, 0.000002)

        self.accept("f3", self.toggle_wireframe)
        self.accept("escape", sys.exit, [0])

        exponential_fog = Fog('world_fog')
        exponential_fog.set_color(0.6, 0.7, 0.7)
        # this is a very low fog value, set it higher for a greater effect
        exponential_fog.set_exp_density(0.00009)
        self.render.set_fog(exponential_fog)

        self.game_start = 0

        from panda3d.bullet import BulletWorld
        from panda3d.bullet import BulletCharacterControllerNode
        from panda3d.bullet import ZUp
        from panda3d.bullet import BulletCapsuleShape
        from panda3d.bullet import BulletTriangleMesh
        from panda3d.bullet import BulletTriangleMeshShape
        from panda3d.bullet import BulletBoxShape
        from panda3d.bullet import BulletGhostNode
        from panda3d.bullet import BulletRigidBodyNode
        from panda3d.bullet import BulletPlaneShape

        self.world = BulletWorld()
        self.world.set_gravity(Vec3(0, 0, -9.81))

        arena_1 = self.loader.load_model('models/arena_1.gltf')
        arena_1.reparent_to(self.render)
        arena_1.set_pos(0, 0, 0)

        def make_collision_from_model(input_model, node_number, mass, world,
                                      target_pos, h_adj):
            # tristrip generation from static models
            # generic tri-strip collision generator begins
            geom_nodes = input_model.find_all_matches('**/+GeomNode')
            geom_nodes = geom_nodes.get_path(node_number).node()
            # print(geom_nodes)
            geom_target = geom_nodes.get_geom(0)
            # print(geom_target)
            output_bullet_mesh = BulletTriangleMesh()
            output_bullet_mesh.add_geom(geom_target)
            tri_shape = BulletTriangleMeshShape(output_bullet_mesh,
                                                dynamic=False)
            print(output_bullet_mesh)

            body = BulletRigidBodyNode('input_model_tri_mesh')
            np = self.render.attach_new_node(body)
            np.node().add_shape(tri_shape)
            np.node().set_mass(mass)
            np.node().set_friction(0.01)
            np.set_pos(target_pos)
            np.set_scale(1)
            np.set_h(h_adj)
            # np.set_p(180)
            # np.set_r(180)
            np.set_collide_mask(BitMask32.allOn())
            world.attach_rigid_body(np.node())

        make_collision_from_model(arena_1, 0, 0, self.world,
                                  (arena_1.get_pos()), 0)

        # load the scene shader
        scene_shader = Shader.load(Shader.SL_GLSL,
                                   "shaders/simplepbr_vert_mod_1.vert",
                                   "shaders/simplepbr_frag_mod_1.frag")
        self.render.set_shader(scene_shader)
        self.render.set_antialias(AntialiasAttrib.MMultisample)
        scene_shader_attrib = ShaderAttrib.make(scene_shader)
        scene_shader_attrib = scene_shader_attrib.setFlag(
            ShaderAttrib.F_hardware_skinning, True)

        # initialize player character physics the Bullet way
        shape_1 = BulletCapsuleShape(0.75, 0.5, ZUp)
        player_node = BulletCharacterControllerNode(
            shape_1, 0.1, 'Player')  # (shape, mass, player name)
        # player_node.set_max_slope(0.1)
        # player_node.set_linear_movement(1, True)
        player_np = self.render.attach_new_node(player_node)
        player_np.set_pos(-20, -10, 10)
        player_np.set_collide_mask(BitMask32.allOn())
        self.world.attach_character(player_np.node())
        # cast player_np to self.player
        self.player = player_np

        # reparent player character to render node
        fp_character = actor_data.player_character
        fp_character.reparent_to(self.render)
        fp_character.set_scale(1)
        # set the actor skinning hardware shader
        fp_character.set_attrib(scene_shader_attrib)

        self.camera.reparent_to(self.player)
        # reparent character to FPS cam
        fp_character.reparent_to(self.player)
        fp_character.set_pos(0, 0, -0.95)
        # self.camera.set_x(self.player, 1)
        self.camera.set_y(self.player, 0.03)
        self.camera.set_z(self.player, 0.5)

        # player gun begins
        self.player_gun = actor_data.arm_handgun
        self.player_gun.reparent_to(self.render)
        self.player_gun.reparent_to(self.camera)
        self.player_gun.set_x(self.camera, 0.1)
        self.player_gun.set_y(self.camera, 0.4)
        self.player_gun.set_z(self.camera, -0.1)

        # directly make a text node to display text
        text_1 = TextNode('text_1_node')
        text_1.set_text("")
        text_1_node = self.aspect2d.attach_new_node(text_1)
        text_1_node.set_scale(0.05)
        text_1_node.set_pos(-1.4, 0, 0.92)
        # import font and set pixels per unit font quality
        nunito_font = loader.load_font('fonts/Nunito/Nunito-Light.ttf')
        nunito_font.set_pixels_per_unit(100)
        nunito_font.set_page_size(512, 512)
        # apply font
        text_1.set_font(nunito_font)
        # small caps
        # text_1.set_small_caps(True)

        # on-screen target dot for aiming
        target_dot = TextNode('target_dot_node')
        target_dot.set_text(".")
        target_dot_node = self.aspect2d.attach_new_node(target_dot)
        target_dot_node.set_scale(0.075)
        target_dot_node.set_pos(0, 0, 0)
        # target_dot_node.hide()
        # apply font
        target_dot.set_font(nunito_font)
        target_dot.set_align(TextNode.ACenter)
        # see the Task section for relevant dot update logic

        # directly make a text node to display text
        text_2 = TextNode('text_2_node')
        text_2.set_text("Neutralize the NPC by shooting the head." + '\n' +
                        "Press 'f' to toggle the flashlight.")
        text_2_node = self.aspect2d.attach_new_node(text_2)
        text_2_node.set_scale(0.04)
        text_2_node.set_pos(-1.4, 0, 0.8)
        # import font and set pixels per unit font quality
        nunito_font = self.loader.load_font('fonts/Nunito/Nunito-Light.ttf')
        nunito_font.set_pixels_per_unit(100)
        nunito_font.set_page_size(512, 512)
        # apply font
        text_2.set_font(nunito_font)
        text_2.set_text_color(0, 0.3, 1, 1)

        # print player position on mouse click
        def print_player_pos():
            print(self.player.get_pos())
            self.player.node().do_jump()

        self.accept('mouse3', print_player_pos)

        self.flashlight_state = 0

        def toggle_flashlight():
            current_flashlight = self.render.find_all_matches("**/flashlight")

            if self.flashlight_state == 0:
                if len(current_flashlight) == 0:
                    self.slight = 0
                    self.slight = Spotlight('flashlight')
                    self.slight.set_shadow_caster(True, 1024, 1024)
                    self.slight.set_color(VBase4(0.5, 0.6, 0.6,
                                                 1))  # slightly bluish
                    lens = PerspectiveLens()
                    lens.set_near_far(0.5, 5000)
                    self.slight.set_lens(lens)
                    self.slight.set_attenuation((0.5, 0, 0.0000005))
                    self.slight = self.render.attach_new_node(self.slight)
                    self.slight.set_pos(-0.1, 0.3, -0.4)
                    self.slight.reparent_to(self.camera)
                    self.flashlight_state = 1
                    self.render.set_light(self.slight)

                elif len(current_flashlight) > 0:
                    self.render.set_light(self.slight)
                    self.flashlight_state = 1

            elif self.flashlight_state > 0:
                self.render.set_light_off(self.slight)
                self.flashlight_state = 0

        self.accept('f', toggle_flashlight)

        # add a few random physics boxes
        for x in range(0, 40):
            # dynamic collision
            random_vec = Vec3(1, 1, 1)
            special_shape = BulletBoxShape(random_vec)
            # rigidbody
            body = BulletRigidBodyNode('random_prisms')
            d_coll = self.render.attach_new_node(body)
            d_coll.node().add_shape(special_shape)
            d_coll.node().set_mass(0.9)
            d_coll.node().set_friction(0.5)
            d_coll.set_collide_mask(BitMask32.allOn())
            # turn on Continuous Collision Detection
            d_coll.node().set_ccd_motion_threshold(0.000000007)
            d_coll.node().set_ccd_swept_sphere_radius(0.30)
            d_coll.node().set_deactivation_enabled(False)
            d_coll.set_pos(random.uniform(-60, -20), random.uniform(-60, -20),
                           random.uniform(5, 10))
            box_model = self.loader.load_model('models/1m_cube.gltf')
            box_model.reparent_to(self.render)
            box_model.reparent_to(d_coll)
            box_model.set_color(random.uniform(0, 1), random.uniform(0, 1),
                                random.uniform(0, 1), 1)
            self.world.attach_rigid_body(d_coll.node())

        # portal #1 begins
        # make a new texture buffer, render node, and attach a camera
        mirror_buffer = self.win.make_texture_buffer("mirror_buff", 4096, 4096)
        mirror_render = NodePath("mirror_render")
        mirror_render.set_shader(scene_shader)
        self.mirror_cam = self.make_camera(mirror_buffer)
        self.mirror_cam.reparent_to(mirror_render)
        self.mirror_cam.set_pos(0, -60, 5)
        self.mirror_cam.set_hpr(0, 25, 0)
        self.mirror_cam.node().get_lens().set_focal_length(10)
        self.mirror_cam.node().get_lens().set_fov(90)

        mirror_filters = CommonFilters(mirror_buffer, self.mirror_cam)
        # mirror_filters.set_high_dynamic_range()
        mirror_filters.set_exposure_adjust(1.1)
        # mirror_filters.set_gamma_adjust(1.3)

        # load in a mirror/display object model in normal render space
        self.mirror_model = self.loader.loadModel(
            'models/wide_screen_video_display.egg')
        self.mirror_model.reparent_to(self.render)
        self.mirror_model.set_pos(-20, 0, 1)
        self.mirror_model.set_sz(3)
        # self.mirror_model.flatten_strong()

        # mirror scene model load-in
        # reparent to mirror render node
        house_uv = self.loader.load_model('models/hangar_1.gltf')
        house_uv.reparent_to(mirror_render)
        windows = house_uv.find('**/clear_arches')
        windows.hide()
        house_uv.set_pos(0, 0, 0)
        house_uv.set_scale(1)

        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(mirror_render)
        house_uv.set_h(180)
        house_uv.set_scale(1.5)
        house_uv.set_pos(0, -50, 0)

        # mirror scene lighting
        # point light generator
        for x in range(0, 1):
            plight_1 = PointLight('plight')
            # add plight props here
            plight_1_node = mirror_render.attach_new_node(plight_1)
            # group the lights close to each other to create a sun effect
            plight_1_node.set_pos(random.uniform(-21, -20),
                                  random.uniform(-21, -20),
                                  random.uniform(20, 21))
            mirror_render.set_light(plight_1_node)

        # set the live buffer texture to the mirror/display model in normal render space
        self.mirror_model.set_texture(mirror_buffer.get_texture())

        # secret hangar far off somewhere on the graph
        house_uv = self.loader.load_model('models/hangar_1.gltf')
        house_uv.reparent_to(self.render)
        windows = house_uv.find('**/clear_arches')
        windows.hide()
        house_uv.set_pos(400, 400, -1)

        # the portal ring
        house_uv = self.loader.load_model('models/ring_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(90)
        house_uv.set_pos(-20, 0, -2)

        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(0)
        house_uv.set_pos(-20, -5.5, 0)
        r_pos = house_uv.get_pos()
        make_collision_from_model(house_uv, 0, 0, self.world,
                                  (r_pos[0], r_pos[1], r_pos[2] + 1), 0)

        self.count_frames_1 = 0
        self.screen_cap_num = 1

        def make_screenshot():
            # Ensure the frame is rendered.
            base.graphicsEngine.render_frame()

            # Grab the screenshot into a big image
            full = PNMImage()
            base.win.get_screenshot(full)

            # Now reduce it
            reduced = PNMImage(500, 300)
            reduced.gaussianFilterFrom(1, full)

            # And write it out.
            reduced.write(
                Filename('screen_cap_' + str(self.screen_cap_num) + '.jpg'))

            self.screen_cap_num += 1

        def update_portal_cam(Task):
            if self.count_frames_1 < 30:
                self.count_frames_1 += 1

            if self.count_frames_1 == 15:
                pass
                # make_screenshot()

            if self.count_frames_1 == 29:
                self.count_frames_1 = 0

            p_dist = (self.player.get_pos() -
                      self.mirror_model.get_pos(base.render)).length()
            target_fov = 115

            if p_dist < 2.25:
                target_fov = 145
                self.player.set_pos(400, 400, 3)
                # adjust the ground plane
                self.ground_plane.set_pos(0, 0, 0)
                # move the normal point lights
                lights = self.render.find_all_matches('**/plight*')
                for l in lights:
                    l.set_pos(400, 400, 21)

            player_h = self.player.get_h()
            self.mirror_cam.set_h(player_h)
            self.mirror_cam.node().get_lens().set_fov(target_fov)

            return Task.cont

        self.task_mgr.add(update_portal_cam)

        # portal #2 begins
        # the portal ring
        house_uv = self.loader.load_model('models/ring_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(90)
        house_uv.set_pos(400, 400, -2)

        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(180)
        house_uv.set_pos(400, 405.5, 0)
        r_pos = house_uv.get_pos()
        make_collision_from_model(house_uv, 0, 0, self.world,
                                  (r_pos[0], r_pos[1], r_pos[2] + 1), 180)

        # NPC_1 load-in
        comp_shape_1 = BulletCapsuleShape(0.05, 0.01, ZUp)
        npc_1_node = BulletCharacterControllerNode(
            comp_shape_1, 0.2, 'NPC_A_node')  # (shape, mass, character name)
        np = self.render.attach_new_node(npc_1_node)
        np.set_pos(-40, -40, 5)
        np.set_collide_mask(BitMask32.allOn())
        self.world.attach_character(np.node())
        np.set_h(random.randint(0, 180))
        npc_model_1 = actor_data.NPC_1
        npc_model_1.reparent_to(np)
        # set the actor skinning hardware shader
        npc_model_1.set_attrib(scene_shader_attrib)
        # get the separate head model
        npc_1_head = self.loader.load_model('models/npc_1_head.gltf')
        npc_1_head.reparent_to(actor_data.NPC_1.get_parent())

        # npc base animation loop
        npc_1_control = actor_data.NPC_1.get_anim_control('walking')
        if not npc_1_control.is_playing():
            actor_data.NPC_1.stop()
            actor_data.NPC_1.loop("walking", fromFrame=60, toFrame=180)
            actor_data.NPC_1.set_play_rate(6.0, 'walking')

        # create special hit areas
        # use Task section for npc collision movement logic
        # special head node size
        special_shape = BulletBoxShape(Vec3(0.1, 0.1, 0.1))
        # ghost npc node
        body = BulletGhostNode('special_node_A')
        special_node = self.render.attach_new_node(body)
        special_node.node().add_shape(
            special_shape, TransformState.makePos(Point3(0, 0, 0.4)))
        # special_node.node().set_mass(0)
        # special_node.node().set_friction(0.5)
        special_node.set_collide_mask(BitMask32(0x0f))
        # turn on Continuous Collision Detection
        special_node.node().set_deactivation_enabled(False)
        special_node.node().set_ccd_motion_threshold(0.000000007)
        special_node.node().set_ccd_swept_sphere_radius(0.30)
        self.world.attach_ghost(special_node.node())

        # dynamic collision
        special_shape = BulletBoxShape(Vec3(0.3, 0.15, 0.6))
        # rigidbody npc node
        body = BulletRigidBodyNode('d_coll_A')
        d_coll = self.render.attach_new_node(body)
        d_coll.node().add_shape(special_shape,
                                TransformState.makePos(Point3(0, 0, 0.7)))
        # d_coll.node().set_mass(0)
        d_coll.node().set_friction(0.5)
        d_coll.set_collide_mask(BitMask32.allOn())
        # turn on Continuous Collision Detection
        d_coll.node().set_deactivation_enabled(False)
        d_coll.node().set_ccd_motion_threshold(0.000000007)
        d_coll.node().set_ccd_swept_sphere_radius(0.30)
        self.world.attach_rigid_body(d_coll.node())

        # npc state variables
        self.npc_1_is_dead = False
        self.npc_1_move_increment = Vec3(0, 0, 0)
        self.gun_anim_is_playing = False

        # npc movement timer
        def npc_1_move_gen():
            while not self.npc_1_is_dead:
                m_incs = []
                for x in range(0, 2):
                    m_incs.append(random.uniform(2, 5))

                print('NPC_1 movement increments this cycle: ' + str(m_incs))
                self.npc_1_move_increment[0] = m_incs[0]
                self.npc_1_move_increment[1] = m_incs[1]
                time.sleep(3)
                self.npc_1_move_increment[0] = m_incs[0]
                self.npc_1_move_increment[1] = m_incs[1]
                time.sleep(3)
                self.npc_1_move_increment[0] = (-1 * m_incs[0]) * 2
                self.npc_1_move_increment[1] = (-1 * m_incs[1]) * 2
                time.sleep(3)
                self.npc_1_move_increment[0] = 0
                self.npc_1_move_increment[1] = 0

        # activate the movement timer in a dedicated thread to prevent lockup with .sleep()
        threading2._start_new_thread(npc_1_move_gen, ())

        def is_npc_1_shot():
            # animate the gun
            gun_ctrl = actor_data.arm_handgun.get_anim_control('shoot')
            if not gun_ctrl.is_playing():
                actor_data.arm_handgun.stop()
                actor_data.arm_handgun.play("shoot")
                actor_data.arm_handgun.set_play_rate(15.0, 'shoot')

            # target dot ray test
            # get mouse data
            mouse_watch = base.mouseWatcherNode
            if mouse_watch.has_mouse():
                posMouse = base.mouseWatcherNode.get_mouse()
                posFrom = Point3()
                posTo = Point3()
                base.camLens.extrude(posMouse, posFrom, posTo)
                posFrom = self.render.get_relative_point(base.cam, posFrom)
                posTo = self.render.get_relative_point(base.cam, posTo)
                rayTest = self.world.ray_test_closest(posFrom, posTo)
                target = rayTest.get_node()
                target_dot = self.aspect2d.find_all_matches(
                    "**/target_dot_node")

                if 'special_node_A' in str(target):

                    def npc_cleanup():
                        # the head is hit, the npc is dead
                        self.npc_1_is_dead = True
                        text_2.set_text('Congrats, you have won!')
                        npc_1_control = actor_data.NPC_1.get_anim_control(
                            'walking')
                        if npc_1_control.is_playing():
                            actor_data.NPC_1.stop()
                        npc_1_control = actor_data.NPC_1.get_anim_control(
                            'death')
                        if not npc_1_control.is_playing():
                            actor_data.NPC_1.play('death')

                        # Bullet node removals
                        self.world.remove(target)
                        rigid_target = self.render.find('**/d_coll_A')
                        self.world.remove(rigid_target.node())

                    threading2._start_new_thread(npc_cleanup, ())

        self.accept('mouse1', is_npc_1_shot)

        def smooth_load_physics():
            # this is a patch to speed up the cold start hitch on success condition
            # Bullet node removals
            self.world.remove(special_node.node())
            rigid_target = self.render.find('**/d_coll_A')
            self.world.remove(rigid_target.node())
            print('NPC physics init removed.')

        smooth_load_physics()

        # repeat the NPC physics initialization after smooth_load_physics
        # create special hit areas
        # use Task section for npc collision movement logic
        # special head node size
        special_shape = BulletBoxShape(Vec3(0.1, 0.1, 0.1))
        # ghost npc node
        body = BulletGhostNode('special_node_A')
        special_node = self.render.attach_new_node(body)
        special_node.node().add_shape(
            special_shape, TransformState.makePos(Point3(0, 0, 0.4)))
        # special_node.node().set_mass(0)
        # special_node.node().set_friction(0.5)
        special_node.set_collide_mask(BitMask32(0x0f))
        # turn on Continuous Collision Detection
        special_node.node().set_deactivation_enabled(False)
        special_node.node().set_ccd_motion_threshold(0.000000007)
        special_node.node().set_ccd_swept_sphere_radius(0.30)
        self.world.attach_ghost(special_node.node())

        # dynamic collision
        special_shape = BulletBoxShape(Vec3(0.3, 0.15, 0.6))
        # rigidbody npc node
        body = BulletRigidBodyNode('d_coll_A')
        d_coll = self.render.attach_new_node(body)
        d_coll.node().add_shape(special_shape,
                                TransformState.makePos(Point3(0, 0, 0.7)))

        # d_coll.node().set_mass(0)
        d_coll.node().set_friction(0.5)
        d_coll.set_collide_mask(BitMask32.allOn())
        # turn on Continuous Collision Detection
        d_coll.node().set_deactivation_enabled(False)
        d_coll.node().set_ccd_motion_threshold(0.000000007)
        d_coll.node().set_ccd_swept_sphere_radius(0.30)
        self.world.attach_rigid_body(d_coll.node())

        # 3D player movement system begins
        self.keyMap = {
            "left": 0,
            "right": 0,
            "forward": 0,
            "backward": 0,
            "run": 0,
            "jump": 0
        }

        def setKey(key, value):
            self.keyMap[key] = value

        # define button map
        self.accept("a", setKey, ["left", 1])
        self.accept("a-up", setKey, ["left", 0])
        self.accept("d", setKey, ["right", 1])
        self.accept("d-up", setKey, ["right", 0])
        self.accept("w", setKey, ["forward", 1])
        self.accept("w-up", setKey, ["forward", 0])
        self.accept("s", setKey, ["backward", 1])
        self.accept("s-up", setKey, ["backward", 0])
        self.accept("shift", setKey, ["run", 1])
        self.accept("shift-up", setKey, ["run", 0])
        self.accept("space", setKey, ["jump", 1])
        self.accept("space-up", setKey, ["jump", 0])
        # disable mouse
        self.disable_mouse()

        # the player movement speed
        self.movementSpeedForward = 5
        self.movementSpeedBackward = 5
        self.dropSpeed = -0.2
        self.striveSpeed = 6
        self.ease = -10.0
        self.static_pos_bool = False
        self.static_pos = Vec3()

        def move(Task):
            if self.game_start > 0:
                if not self.npc_1_is_dead:
                    npc_pos_1 = actor_data.NPC_1.get_parent().get_pos()
                    # place head hit box
                    special_node.set_pos(npc_pos_1[0], npc_pos_1[1],
                                         npc_pos_1[2] + 1)
                    special_node.set_h(actor_data.NPC_1.get_h())
                    # dynamic collision node
                    d_coll.set_pos(npc_pos_1[0], npc_pos_1[1], npc_pos_1[2])
                    d_coll.set_h(actor_data.NPC_1.get_h())
                    # make the npc look at the player continuously
                    actor_data.NPC_1.look_at(self.player)
                    npc_1_head.look_at(self.player)

                    if actor_data.NPC_1.get_p() > 3:
                        actor_data.NPC_1.set_p(3)

                    if npc_1_head.get_p() > 3:
                        npc_1_head.set_p(3)

                    m_inst = self.npc_1_move_increment
                    t_inst = globalClock.get_dt()
                    actor_data.NPC_1.get_parent().set_pos(
                        npc_pos_1[0] + (m_inst[0] * t_inst),
                        npc_pos_1[1] + (m_inst[1] * t_inst), npc_pos_1[2])

                if self.npc_1_is_dead:
                    npc_1_head.hide()
                    inst_h = actor_data.NPC_1.get_h()
                    inst_p = actor_data.NPC_1.get_p()
                    actor_data.NPC_1.set_hpr(inst_h, inst_p, 0)

                # target dot ray test
                # turns the target dot red
                # get mouse data
                mouse_watch = base.mouseWatcherNode
                if mouse_watch.has_mouse():
                    posMouse = base.mouseWatcherNode.get_mouse()
                    posFrom = Point3()
                    posTo = Point3()
                    base.camLens.extrude(posMouse, posFrom, posTo)
                    posFrom = self.render.get_relative_point(base.cam, posFrom)
                    posTo = self.render.get_relative_point(base.cam, posTo)
                    rayTest = self.world.ray_test_closest(posFrom, posTo)
                    target = rayTest.get_node()
                    target_dot = self.aspect2d.find_all_matches(
                        "**/target_dot_node")

                    if 'special_node_A' in str(target):
                        # the npc is recognized, make the dot red
                        for dot in target_dot:
                            dot.node().set_text_color(0.9, 0.1, 0.1, 1)

                    if 'd_coll_A' in str(target):
                        # the npc is recognized, make the dot red
                        for dot in target_dot:
                            dot.node().set_text_color(0.9, 0.1, 0.1, 1)

                    if 'special_node_A' not in str(target):
                        # no npc recognized, make the dot white
                        if 'd_coll_A' not in str(target):
                            for dot in target_dot:
                                dot.node().set_text_color(1, 1, 1, 1)

                # get mouse data
                mouse_watch = base.mouseWatcherNode
                if mouse_watch.has_mouse():
                    pointer = base.win.get_pointer(0)
                    mouseX = pointer.get_x()
                    mouseY = pointer.get_y()

                # screen sizes
                window_Xcoord_halved = base.win.get_x_size() // 2
                window_Ycoord_halved = base.win.get_y_size() // 2
                # mouse speed
                mouseSpeedX = 0.2
                mouseSpeedY = 0.2
                # maximum and minimum pitch
                maxPitch = 90
                minPitch = -50
                # cam view target initialization
                camViewTarget = LVecBase3f()

                if base.win.movePointer(0, window_Xcoord_halved,
                                        window_Ycoord_halved):
                    p = 0

                    if mouse_watch.has_mouse():
                        # calculate the pitch of camera
                        p = self.camera.get_p() - (
                            mouseY - window_Ycoord_halved) * mouseSpeedY

                    # sanity checking
                    if p < minPitch:
                        p = minPitch
                    elif p > maxPitch:
                        p = maxPitch

                    if mouse_watch.has_mouse():
                        # directly set the camera pitch
                        self.camera.set_p(p)
                        camViewTarget.set_y(p)

                    # rotate the self.player's heading according to the mouse x-axis movement
                    if mouse_watch.has_mouse():
                        h = self.player.get_h() - (
                            mouseX - window_Xcoord_halved) * mouseSpeedX

                    if mouse_watch.has_mouse():
                        # sanity checking
                        if h < -360:
                            h += 360

                        elif h > 360:
                            h -= 360

                        self.player.set_h(h)
                        camViewTarget.set_x(h)

                    # hide the gun if looking straight down
                    if p < -30:
                        self.player_gun.hide()
                    if p > -30:
                        self.player_gun.show()

                if self.keyMap["left"]:
                    if self.static_pos_bool:
                        self.static_pos_bool = False

                    self.player.set_x(self.player,
                                      -self.striveSpeed * globalClock.get_dt())

                    myAnimControl = actor_data.player_character.get_anim_control(
                        'walking')
                    if not myAnimControl.isPlaying():
                        actor_data.player_character.play("walking")
                        actor_data.player_character.set_play_rate(
                            4.0, 'walking')

                if not self.keyMap["left"]:
                    if not self.static_pos_bool:
                        self.static_pos_bool = True
                        self.static_pos = self.player.get_pos()

                    self.player.set_x(self.static_pos[0])
                    self.player.set_y(self.static_pos[1])
                    self.player.set_z(self.player,
                                      self.dropSpeed * globalClock.get_dt())

                if self.keyMap["right"]:
                    if self.static_pos_bool:
                        self.static_pos_bool = False

                    self.player.set_x(self.player,
                                      self.striveSpeed * globalClock.get_dt())

                    myAnimControl = actor_data.player_character.get_anim_control(
                        'walking')
                    if not myAnimControl.isPlaying():
                        actor_data.player_character.play("walking")
                        actor_data.player_character.set_play_rate(
                            4.0, 'walking')

                if not self.keyMap["right"]:
                    if not self.static_pos_bool:
                        self.static_pos_bool = True
                        self.static_pos = self.player.get_pos()

                    self.player.set_x(self.static_pos[0])
                    self.player.set_y(self.static_pos[1])
                    self.player.set_z(self.player,
                                      self.dropSpeed * globalClock.get_dt())

                if self.keyMap["forward"]:
                    if self.static_pos_bool:
                        self.static_pos_bool = False

                    self.player.set_y(
                        self.player,
                        self.movementSpeedForward * globalClock.get_dt())

                    myAnimControl = actor_data.player_character.get_anim_control(
                        'walking')
                    if not myAnimControl.isPlaying():
                        actor_data.player_character.play("walking")
                        actor_data.player_character.set_play_rate(
                            4.0, 'walking')

                if self.keyMap["forward"] != 1:
                    if not self.static_pos_bool:
                        self.static_pos_bool = True
                        self.static_pos = self.player.get_pos()

                    self.player.set_x(self.static_pos[0])
                    self.player.set_y(self.static_pos[1])
                    self.player.set_z(self.player,
                                      self.dropSpeed * globalClock.get_dt())

                if self.keyMap["backward"]:
                    if self.static_pos_bool:
                        self.static_pos_bool = False

                    self.player.set_y(
                        self.player,
                        -self.movementSpeedBackward * globalClock.get_dt())

                    myBackControl = actor_data.player_character.get_anim_control(
                        'walking')
                    if not myBackControl.isPlaying():
                        myBackControl.stop()
                        actor_data.player_character.play('walking')
                        actor_data.player_character.set_play_rate(
                            -4.0, 'walking')

            return Task.cont

        # infinite ground plane
        # the effective world-Z limit
        ground_plane = BulletPlaneShape(Vec3(0, 0, 1), 0)
        node = BulletRigidBodyNode('ground')
        node.add_shape(ground_plane)
        node.set_friction(0.1)
        self.ground_plane = self.render.attach_new_node(node)
        self.ground_plane.set_pos(0, 0, -1)
        self.world.attach_rigid_body(node)

        # Bullet debugger
        from panda3d.bullet import BulletDebugNode
        debugNode = BulletDebugNode('Debug')
        debugNode.show_wireframe(True)
        debugNode.show_constraints(True)
        debugNode.show_bounding_boxes(False)
        debugNode.show_normals(False)
        debugNP = self.render.attach_new_node(debugNode)
        self.world.set_debug_node(debugNP.node())

        # debug toggle function
        def toggle_debug():
            if debugNP.is_hidden():
                debugNP.show()
            else:
                debugNP.hide()

        self.accept('f1', toggle_debug)

        def update(Task):
            if self.game_start < 1:
                self.game_start = 1
            return Task.cont

        def physics_update(Task):
            dt = globalClock.get_dt()
            self.world.do_physics(dt)
            return Task.cont

        self.task_mgr.add(move)
        self.task_mgr.add(update)
        self.task_mgr.add(physics_update)