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())
class Game(ShowBase): def __init__(self): ShowBase.__init__(self) base.set_background_color(0.1, 0.1, 0.8, 1) base.set_frame_rate_meter(True) base.cam.set_pos_hpr(0, 0, 25, 0, -90, 0) base.disable_mouse() # Input self.accept('escape', self.exitGame) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) # Setup scene 1: World self.debugNP = render.attach_new_node(BulletDebugNode('Debug')) self.debugNP.node().show_wireframe(True) self.debugNP.node().show_constraints(True) self.debugNP.node().show_bounding_boxes(True) self.debugNP.node().show_normals(True) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Setup scene 2: Ball #visNP = loader.load_model('models/ball.egg') visNP = loader.load_model('samples/ball-in-maze/models/ball.egg.pz') visNP.clear_model_nodes() bodyNPs = BulletHelper.from_collision_solids(visNP, True) self.ballNP = bodyNPs[0] self.ballNP.reparent_to(render) self.ballNP.node().set_mass(1.0) self.ballNP.set_pos(4, -4, 1) self.ballNP.node().set_deactivation_enabled(False) visNP.reparent_to(self.ballNP) # Setup scene 3: Maze visNP = loader.load_model('models/maze.egg') #visNP = loader.load_model('samples/ball-in-maze/models/maze.egg.pz') visNP.clear_model_nodes() visNP.reparent_to(render) self.holes = [] self.maze = [] self.mazeNP = visNP bodyNPs = BulletHelper.from_collision_solids(visNP, True); for bodyNP in bodyNPs: bodyNP.reparent_to(render) if isinstance(bodyNP.node(), BulletRigidBodyNode): bodyNP.node().set_mass(0.0) bodyNP.node().set_kinematic(True) self.maze.append(bodyNP) elif isinstance(bodyNP.node(), BulletGhostNode): self.holes.append(bodyNP) # Lighting and material for the ball ambientLight = AmbientLight('ambientLight') ambientLight.set_color(LVector4(0.55, 0.55, 0.55, 1)) directionalLight = DirectionalLight('directionalLight') directionalLight.set_direction(LVector3(0, 0, -1)) directionalLight.set_color(LVector4(0.375, 0.375, 0.375, 1)) directionalLight.set_specular_color(LVector4(1, 1, 1, 1)) self.ballNP.set_light(render.attach_new_node(ambientLight)) self.ballNP.set_light(render.attach_new_node(directionalLight)) m = Material() m.set_specular(LVector4(1,1,1,1)) m.set_shininess(96) self.ballNP.set_material(m, 1) # Startup self.start_game() def exitGame(self): sys.exit() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') def start_game(self): self.ballNP.set_pos(4, -4, 1) self.ballNP.node().set_linear_velocity(LVector3(0, 0, 0)) self.ballNP.node().set_angular_velocity(LVector3(0, 0, 0)) # Mouse p = base.win.get_properties() base.win.move_pointer(0, int(p.get_x_size()/2), int(p.get_y_size()/2)) # Add bodies and ghosts self.world.attach(self.ballNP.node()) for bodyNP in self.maze: self.world.attach(bodyNP.node()) for ghostNP in self.holes: self.world.attach(ghostNP.node()) # Simulation task taskMgr.add(self.update_game, 'updateGame') def stop_game(self): # Remove bodies and ghosts self.world.remove(self.ballNP.node()) for bodyNP in self.maze: self.world.remove(bodyNP.node()) for ghostNP in self.holes: self.world.remove(ghostNP.node()) # Simulation task taskMgr.remove('updateGame') def update_game(self, task): dt = globalClock.get_dt() # Get mouse position and tilt maze if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.get_mouse() hpr = LVector3(0, mpos.y * -10, mpos.x * 10) # Maze visual node self.mazeNP.set_hpr(hpr) # Maze body nodes for bodyNP in self.maze: bodyNP.set_hpr(hpr) # Update simulation self.world.do_physics(dt) # Check if ball is touching a hole for holeNP in self.holes: if holeNP.node().get_num_overlapping_nodes() > 2: if self.ballNP.node() in holeNP.node().get_overlapping_nodes(): self.lose_game(holeNP) return task.cont def lose_game(self, holeNP): toPos = holeNP.node().get_shape_pos(0) self.stop_game() Sequence( Parallel( LerpFunc(self.ballNP.set_x, fromData = self.ballNP.get_x(), toData = toPos.get_x(), duration = .1), LerpFunc(self.ballNP.set_y, fromData = self.ballNP.get_y(), toData = toPos.get_y(), duration = .1), LerpFunc(self.ballNP.set_z, fromData = self.ballNP.get_z(), toData = self.ballNP.get_z() - .9, duration = .2)), Wait(1), Func(self.start_game)).start()
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
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()
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
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()
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
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
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
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
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)
class PhysMgr(PhysFacade): __metaclass__ = Singleton def __init__(self): self.collision_objs = [] # objects to be processed self.__obj2coll = {} # obj: [(node, coll_time), ...] self.root = None self.__debug_np = None def init(self): self.collision_objs = [] self.__obj2coll = {} self.root = BulletWorld() self.root.setGravity((0, 0, -9.81)) debug_node = BulletDebugNode('Debug') debug_node.show_bounding_boxes(True) self.__debug_np = render.attach_new_node(debug_node) self.root.set_debug_node(self.__debug_np.node()) def start(self): eng.attach_obs(self.on_frame, 2) def on_frame(self): self.root.do_physics(globalClock.get_dt(), 10, 1 / 180.0) self.__do_collisions() def add_collision_obj(self, node): self.collision_objs += [node] def stop(self): self.root = None self.__debug_np.removeNode() eng.detach_obs(self.on_frame) def __process_contact(self, obj, node, to_clear): if node == obj: return obj in to_clear and to_clear.remove(obj) if node in [coll[0] for coll in self.__obj2coll[obj]]: return self.__obj2coll[obj] += [(node, globalClock.get_frame_time())] eng.event.notify('on_collision', obj, node.get_name()) def __do_collisions(self): to_clear = self.collision_objs[:] for obj in self.collision_objs: if not obj in self.__obj2coll: self.__obj2coll[obj] = [] result = self.root.contact_test(obj) for contact in result.get_contacts(): self.__process_contact(obj, contact.get_node0(), to_clear) self.__process_contact(obj, contact.get_node1(), to_clear) for obj in to_clear: for coll in self.__obj2coll[obj]: if globalClock.get_frame_time() - coll[1] > .25: self.__obj2coll[obj].remove(coll) def toggle_debug(self): is_hidden = self.__debug_np.is_hidden() (self.__debug_np.show if is_hidden else self.__debug_np.hide)() @staticmethod def find_geoms(model, name): # no need to be cached geoms = model.find_all_matches('**/+GeomNode') is_nm = lambda geom: geom.get_name().startswith(name) named_geoms = [geom for geom in geoms if is_nm(geom)] in_vec = [name in named_geom.get_name() for named_geom in named_geoms] indexes = [i for i, el in enumerate(in_vec) if el] return [named_geoms[i] for i in indexes]