def create_solid(self): node = BulletRigidBodyNode(self.name) node.set_angular_damping(.9) node_shape = BulletSphereShape(.08) node.add_shape(node_shape) node.set_mass(.5) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape( BulletBoxShape( Vec3(self.size[0] / 2.0, self.size[1] / 2.0, self.size[2] / 2.0))) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node.set_angular_damping(.9) node_shape = BulletSphereShape(.08) node.add_shape(node_shape) node.set_mass(.5) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node_shape = BulletBoxShape(Vec3(.01, self.size, self.size)) node.add_shape(node_shape) node.set_mass(3) node.set_angular_damping(.7) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node_shape = BulletBoxShape(Vec3(.01, self.size, self.size)) node.add_shape(node_shape) node.set_mass(3) node.set_angular_damping(.7) return node
def update_root_nodepath(self, nodepath): entity = self._entity component = self._class_component # Find appropriate mesh source mesh_name = component.mesh_name if mesh_name is None: physics_node = BulletRigidBodyNode() # Load mesh from first MeshComponent for cls_component in entity.components.values(): if isinstance(cls_component, MeshComponent): shape = self._shape_from_mesh_component( entity, cls_component) physics_node.add_shape(shape) break else: physics_node = self._node_from_bam_name(entity, mesh_name) # Set mass if component.mass is not None: physics_node.set_mass(component.mass) physics_node.notify_collisions(True) physics_node.set_deactivation_enabled(False) physics_nodepath = NodePath(physics_node) nodepath.reparent_to(physics_nodepath) self.body = physics_node self._nodepath = physics_nodepath return physics_nodepath
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape( BulletBoxShape( Vec3(self.thickness / 2.0, self.width / 2.0, self.length / 2.0))) return node
class WeaponPhys(PhysColleague): def __init__(self, mediator, car, cars, players): # unused players PhysColleague.__init__(self, mediator) self.parent, self.car, self.cars = car.gfx.nodepath, car, cars self.n_p = self.node = None def fire(self): self.node = BulletRigidBodyNode(self.coll_name) self.node.set_mass(50) self.node.add_shape(self.coll_mesh_cls(.5)) self.n_p = self.parent.attach_node(self.node) self.n_p.set_pos(Vec(0, 0, self.joint_z)) self.n_p.wrt_reparent_to(self.eng.gfx.root) launch_dist = self.car.logic.car_vec * self.launch_dist pos = self.n_p.get_pos() pos = Vec(pos.x, pos.y, pos.z) self.n_p.set_pos(pos + launch_dist) self.eng.phys_mgr.attach_rigid_body(self.node) self.mediator.gfx.gfx_np.reparent_to(self.n_p) self.mediator.gfx.gfx_np.set_pos(Vec(0, 0, self.gfx_dz)) def destroy(self): if self.node: # has not been fired self.eng.phys_mgr.remove_rigid_body(self.node) try: self.n_p = self.n_p.remove_node() except AttributeError: info('n_p: %s' % self.n_p) # it may happen on pause/menu self.parent = None PhysColleague.destroy(self)
def add_ball(task): bullet_body = BulletRigidBodyNode() bullet_body.set_linear_sleep_threshold(0) bullet_body.set_angular_sleep_threshold(0) bullet_body.set_mass(1.0) bullet_body.add_shape(BulletSphereShape(ball_size)) func = random.choice([ add_smiley, add_panda, add_mr_man_static, add_mr_man_dynamic, ]) func(world, bullet_body) return task.again
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 drop_boxes(self): """ Puts a stack of boxes at a distance in front of the vehicle. The boxes will not necessarily spawn above the terrain and will not be cleaned up. """ model = loader.load_model('models/box.egg') model.set_pos(-0.5, -0.5, -0.5) model.flatten_light() shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) ahead = self.vehicleNP.get_pos() + self.vehicle.get_forward_vector()*15 for i in range(6): node = BulletRigidBodyNode('Box') node.set_mass(5.0) node.add_shape(shape) node.set_deactivation_enabled(False) np = render.attach_new_node(node) np.set_pos(ahead.x, ahead.y, ahead.z + i*2) self.world.attach(node) model.copy_to(np)
def generate_spheroid(self): bounding_box = Vec3(*[random.uniform(5.0, 30.0) for _ in range(3)]) pos = Vec3( *[random.uniform(-100.0, 100.0) for _ in range(2)], random.uniform(50.0, 400.0) ) hpr = Vec3(*[random.random() * 360 for _ in range(3)]) s = Spheroid(bounding_box) s.subdivide_dist(4) s.geom_store.normals_as_colors() geom_node = s.geom_node spheroid_np = self.render.attach_new_node(geom_node) shape = get_bullet_shape(geom_node) node = BulletRigidBodyNode('Cuboid') node.set_mass(random.uniform(0.1, 1000.0)) node.add_shape(shape) visual_np = self.render.attach_new_node(node) self.world.attach(node) spheroid_np.reparent_to(visual_np) self.follow_np.reparent_to(visual_np) visual_np.set_pos(pos) visual_np.set_hpr(hpr)
def generate_cylinder(self): start_point = Vec3(*[random.uniform(-30.0, 30.0) for _ in range(3)]) end_point = -start_point radii = [random.randint(0, 10) for _ in range(2)] pos = Vec3( *[random.uniform(-100.0, 100.0) for _ in range(2)], random.uniform(50.0, 400.0) ) c = SingleCylinder(12, start_point, radii[0], end_point, radii[1]) c.subdivide_dist(4) c.geom_store.normals_as_colors() geom_node = c.geom_node cylinder_np = self.render.attach_new_node(geom_node) shape = get_bullet_shape(geom_node) node = BulletRigidBodyNode('Cuboid') node.set_mass(random.uniform(0.1, 1000.0)) node.add_shape(shape) visual_np = self.render.attach_new_node(node) self.world.attach(node) cylinder_np.reparent_to(visual_np) self.follow_np.reparent_to(visual_np) visual_np.set_pos(pos)
def generate_plane(self): bounds = Vec3(*[random.uniform(5.0, 30.0) for _ in range(2)]) pos = Vec3( *[random.uniform(-100.0, 100.0) for _ in range(2)], random.uniform(50.0, 400.0) ) normal = Vec3( *[random.uniform(-1.0, 1.0) for _ in range(3)], ) color = tuple(normal * 0.5 + 0.5) + (1.,) p = Plane((0., 0., 0.), bounds, normal, two_sided=True) p.subdivide_dist(4) p.geom_store.set_color(color) geom_node = p.geom_node plane_np = self.render.attach_new_node(geom_node) shape = get_bullet_shape(geom_node) node = BulletRigidBodyNode('Plane') node.set_mass(random.uniform(0.1, 1000.0)) node.add_shape(shape) visual_np = self.render.attach_new_node(node) self.world.attach(node) plane_np.reparent_to(visual_np) self.follow_np.reparent_to(visual_np) visual_np.set_pos(pos)
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape(BulletBoxShape(Vec3(self.size[0] / 2.0, self.size[1] / 2.0, self.size[2] / 2.0))) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape(BulletBoxShape(Vec3(self.thickness / 2.0, self.width / 2.0, self.length / 2.0))) return node
def setup_terrain(self): """ Terrain info Units are meters, which is preferable when working with Bullet. """ self.terrain_scale = LVector3(512, 512, 100) self.terrain_pos = LVector3(-256, -256, -70) # sample values for a 4096 x 4096px heightmap. #self.terrain_scale = LVector3(4096, 4096, 1000) #self.terrain_pos = LVector3(-2048, -2048, -70) """ Diamond_subdivision is an alternating triangulation scheme and may produce better results. """ use_diamond_subdivision = True """ Construct the terrain Without scaling, any ShaderTerrainMesh is 1x1x1 units. """ self.terrain_node = ShaderTerrainMesh() """ Set a heightfield, the heightfield should be a 16-bit png and have a quadratic size of a power of two. """ heightfield = Texture() heightfield.read(self.heightfield_fn) heightfield.set_keep_ram_image(True) self.terrain_node.heightfield = heightfield # Display characteristic values of the heightfield texture #minpoint, maxpoint, avg = LPoint3(), LPoint3(), LPoint3() #heightfield.calc_min_max(minpoint, maxpoint) #heightfield.calc_average_point(avg, 0.5, 0.5, 0.5) #print("avg: {} min: {} max: {}".format(avg.x, minpoint.x, maxpoint.x)) """ Set the target triangle width. For a value of 10.0 for example, the ShaderTerrainMesh will attempt to make every triangle 10 pixels wide on screen. """ self.terrain_node.target_triangle_width = 10.0 if use_diamond_subdivision: """ This has to be specified before calling .generate() The default is false. """ load_prc_file_data("", "stm-use-hexagonal-layout true") self.terrain_node.generate() """ Attach the terrain to the main scene and set its scale. With no scale set, the terrain ranges from (0, 0, 0) to (1, 1, 1) """ self.terrain = self.render.attach_new_node(self.terrain_node) self.terrain.set_scale(self.terrain_scale) self.terrain.set_pos(self.terrain_pos) """ Set a vertex and a fragment shader on the terrain. The ShaderTerrainMesh only works with an applied shader. """ terrain_shader = Shader.load(Shader.SL_GLSL, "samples/shader-terrain/terrain.vert.glsl", "samples/shader-terrain/terrain.frag.glsl") self.terrain.set_shader(terrain_shader) self.terrain.set_shader_input("camera", base.camera) # Set some texture on the terrain grass_tex = self.loader.load_texture( "samples/shader-terrain/textures/grass.png") grass_tex.set_minfilter(SamplerState.FT_linear_mipmap_linear) grass_tex.set_anisotropic_degree(16) self.terrain.set_texture(grass_tex) """ Set up the DynamicHeightfield (it's a type of PfmFile). We load the same heightfield image as with ShaderTerrainMesh. """ self.DHF = DynamicHeightfield() self.DHF.read(self.heightfield_fn) """ Set up empty PfmFiles to prepare stuff in that is going to dynamically modify our terrain. """ self.StagingPFM = PfmFile() self.RotorPFM = PfmFile() """ Set up the BulletHeightfieldShape (=collision terrain) and give it some sensible physical properties. """ self.HFS = BulletHeightfieldShape(self.DHF, self.terrain_scale.z, STM=True) if use_diamond_subdivision: self.HFS.set_use_diamond_subdivision(True) HFS_rigidbody = BulletRigidBodyNode("BulletTerrain") HFS_rigidbody.set_static(True) friction = 2.0 HFS_rigidbody.set_anisotropic_friction( LVector3(friction, friction, friction/1.3)) HFS_rigidbody.set_restitution(0.3) HFS_rigidbody.add_shape(self.HFS) self.world.attach(HFS_rigidbody) HFS_NP = NodePath(HFS_rigidbody) HFS_NP.reparent_to(self.worldNP) """ This aligns the Bullet terrain with the ShaderTerrainMesh rendered terrain. It will be exact as long as the terrain vertex shader from the STM sample is used and no additional tessellation shader. For Bullet (as for other physics engines) the origin of objects is at the center. """ HFS_NP.set_pos(self.terrain_pos + self.terrain_scale/2) HFS_NP.set_sx(self.terrain_scale.x / heightfield.get_x_size()) HFS_NP.set_sy(self.terrain_scale.y / heightfield.get_y_size()) # Disables Bullet debug rendering for the terrain, because it is slow. #HFS_NP.node().set_debug_enabled(False) """ Finally, link the ShaderTerrainMesh and the BulletHeightfieldShape to the DynamicHeightfield. From now on changes to the DynamicHeightfield will propagate to the (visible) ShaderTerrainMesh and the (collidable) BulletHeightfieldShape. """ self.HFS.set_dynamic_heightfield(self.DHF) self.terrain_node.set_dynamic_heightfield(self.DHF)
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)
def make_node(name, BulletShape, *args): # Returns a BulletRigidBodyNode for the given shape shape = BulletShape(*args) node = BulletRigidBodyNode(name) node.add_shape(shape) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) mesh = BulletConvexHullShape() mesh.add_geom(self.geom.get_geom(0)) node.add_shape(mesh) return node
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()
debug_node = BulletDebugNode('Debug') debug_node.showWireframe(True) debug_node.showConstraints(True) 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)
class Vehicle: def __init__(self, app, model_name): model_file_name = 'assets/cars/{}/{}.bam'.format( model_name, model_name) self.app = app def animation_path(model, animation): base_path = 'assets/cars/animations/{}/{}-{}.bam' return base_path.format(model, model, animation) self.model = Actor( model_file_name, { # AIRBRAKE: 'assets/cars/animations/{}-{}.bam'.format(model_name, AIRBRAKE), # AIRBRAKE: animation_path(model_name, AIRBRAKE), # STABILIZER_FINS: animation_path(model_name, STABILIZER_FINS), }) self.model.enableBlend() self.model.setControlEffect(AIRBRAKE, 1) self.model.setControlEffect(STABILIZER_FINS, 1) # FIXME: This code fails due to a bug in Actor # airbrake_joints = [joint.name # for joint in self.model.getJoints() # if joint.name.startswith(AIRBRAKE) # ] # self.model.makeSubpart(AIRBRAKE, airbrake_joints) # stabilizer_joints = [joint.name # for joint in self.model.getJoints() # if joint.name.startswith(STABILIZER_FINS) # ] # self.model.makeSubpart(STABILIZER_FINS, stabilizer_joints) puppet = self.app.loader.load_model(model_file_name) puppet.find("**/armature").hide() puppet.reparentTo(self.model) # Get the vehicle data self.vehicle_data = VehicleData(puppet, model_name, 'cars') # Configure the physics node self.physics_node = BulletRigidBodyNode('vehicle') self.physics_node.set_friction(self.vehicle_data.friction) self.physics_node.set_linear_sleep_threshold(0) self.physics_node.set_angular_sleep_threshold(0) self.physics_node.setCcdMotionThreshold(1e-7) self.physics_node.setCcdSweptSphereRadius(0.5) self.physics_node.setMass(self.vehicle_data.mass) shape = BulletConvexHullShape() for geom_node in self.model.find_all_matches("**/+GeomNode"): for geom in geom_node.node().get_geoms(): vertices = GeomVertexReader(geom.get_vertex_data(), 'vertex') while not vertices.is_at_end(): v_geom = vertices.getData3f() v_model = self.model.get_relative_point(geom_node, v_geom) shape.add_point(v_model) self.physics_node.add_shape(shape) self.vehicle = NodePath(self.physics_node) self.vehicle.set_collide_mask(CM_VEHICLE | CM_COLLIDE) self.model.reparent_to(self.vehicle) # Navigational aids self.target_node = self.app.loader.load_model('models/zup-axis') self.target_node.reparent_to(self.model) self.target_node.set_scale(1) self.target_node.set_render_mode_wireframe() self.target_node.hide() self.delta_node = self.app.loader.load_model('models/smiley') self.delta_node.set_pos(1, 10, 1) self.delta_node.reparent_to(base.cam) self.delta_node.hide() self.airbrake_state = 0.0 self.airbrake_factor = 0.5 self.airbrake_speed = 1 / self.vehicle_data.airbrake_duration self.stabilizer_fins_state = 0.0 self.stabilizer_fins_speed = 1 / self.vehicle_data.stabilizer_fins_duration self.centroid = base.loader.load_model('models/smiley') self.centroid.reparent_to(self.vehicle) self.centroid.hide() # Gyro sound sound_file = 'assets/cars/{}/{}.wav'.format( model_name, GYROSCOPE_SOUND, ) sound = base.audio3d.load_sfx(sound_file) self.model.set_python_tag(GYROSCOPE_SOUND, sound) base.audio3d.attach_sound_to_object(sound, self.model) sound.set_volume(0) sound.set_play_rate(0) sound.set_loop(True) sound.play() # Thruster limiting self.thruster_state = 0.0 self.thruster_heat = 0.0 for repulsor in self.vehicle_data.repulsor_nodes: self.add_repulsor(repulsor, model_name) for thruster in self.vehicle_data.thruster_nodes: self.add_thruster(thruster, model_name) # ECU data storage from frame to frame self.last_flight_height = None # FIXME: Move into a default controller self.inputs = { # Repulsors REPULSOR_ACTIVATION: 0.0, ACCELERATE: 0.0, TURN: 0.0, STRAFE: 0.0, HOVER: 0.0, FULL_REPULSORS: False, # Gyro ACTIVE_STABILIZATION_ON_GROUND: PASSIVE, ACTIVE_STABILIZATION_CUTOFF_ANGLE: PASSIVE, ACTIVE_STABILIZATION_IN_AIR: PASSIVE, TARGET_ORIENTATION: Vec3(0, 0, 0), # Thrust THRUST: 0.0, # Air foils AIRBRAKE: 0.0, STABILIZER_FINS: 0.0, } self.sensors = {} self.commands = {} def np(self): return self.vehicle def place(self, spawn_point): # FIXME: Pass a root node to __init__ instead self.vehicle.reparent_to(self.app.environment.model) connector = self.model.find("**/" + SPAWN_POINT_CONNECTOR) self.vehicle.set_hpr(-connector.get_hpr(spawn_point)) self.vehicle.set_pos(-connector.get_pos(spawn_point)) self.app.environment.add_physics_node(self.physics_node) def set_inputs(self, inputs): self.inputs = inputs def add_repulsor(self, node, model_name): ground_contact = self.app.loader.load_model( "assets/effect/repulsorhit.egg") ground_contact.set_scale(1) ground_contact.reparent_to(self.app.render) node.set_python_tag('ray_end', ground_contact) sound_file = 'assets/cars/{}/{}.wav'.format( model_name, REPULSOR_SOUND, ) sound = base.audio3d.load_sfx(sound_file) node.set_python_tag(REPULSOR_SOUND, sound) base.audio3d.attach_sound_to_object(sound, node) sound.set_volume(0) sound.set_play_rate(0) sound.set_loop(True) sound.play() def add_thruster(self, node, model_name): node.set_python_tag(THRUSTER_POWER, 0.0) node.set_python_tag(THRUSTER_OVERHEATED, False) # Basic jet sound sound_file = 'assets/cars/{}/{}.wav'.format( model_name, THRUSTER_SOUND, ) sound = base.audio3d.load_sfx(sound_file) node.set_python_tag(THRUSTER_SOUND, sound) base.audio3d.attach_sound_to_object(sound, node) sound.set_volume(0) sound.set_play_rate(0) sound.set_loop(True) sound.play() # Overheating sound sound_file = 'assets/cars/{}/{}.wav'.format( model_name, THRUSTER_OVERHEAT_SOUND, ) sound = base.audio3d.load_sfx(sound_file) node.set_python_tag(THRUSTER_OVERHEAT_SOUND, sound) base.audio3d.attach_sound_to_object(sound, node) def game_loop(self): self.gather_sensors() self.ecu() self.apply_air_drag() self.apply_repulsors() self.apply_gyroscope() self.apply_thrusters() self.apply_airbrake() self.apply_stabilizer_fins() def gather_sensors(self): # Gather data repulsor ray collisions with ground repulsor_data = [] for node in self.vehicle_data.repulsor_nodes: data = RepulsorData() repulsor_data.append(data) max_distance = node.get_python_tag(ACTIVATION_DISTANCE) data.position = node.get_pos(self.vehicle) data.direction = self.app.render.get_relative_vector( node, Vec3(0, 0, -max_distance), ) # FIXME: `self.app.environment.physics_world` is ugly. feeler = self.app.environment.physics_world.ray_test_closest( base.render.get_relative_point(self.vehicle, data.position), base.render.get_relative_point(self.vehicle, data.position + data.direction), CM_TERRAIN, ) if feeler.has_hit(): data.active = True data.fraction = feeler.get_hit_fraction() data.contact = self.vehicle.get_relative_point( self.app.render, feeler.get_hit_pos(), ) else: data.active = False data.fraction = 1.0 # Find the local ground's normal vector local_up = False contacts = [ data.contact for data in repulsor_data if data.active and self.inputs[REPULSOR_ACTIVATION] ] if len(contacts) > 2: local_up = True target_mode = self.inputs[ACTIVE_STABILIZATION_ON_GROUND] # We can calculate a local up as the smallest base vector of the # point cloud of contacts. centroid = reduce(lambda a, b: a + b, contacts) / len(contacts) covariance = [ [c.x, c.y, c.z] for c in [contact - centroid for contact in contacts] ][0:3] # We need exactly 3, no PCA yet. :( eigenvalues, eigenvectors = numpy.linalg.eig( numpy.array(covariance)) # FIXME: These few lines look baaaad... indexed_eigenvalues = enumerate(eigenvalues) def get_magnitude(indexed_element): index, value = indexed_element return abs(value) sorted_indexed_eigenvalues = sorted( indexed_eigenvalues, key=get_magnitude, ) # The smallest eigenvalue leads to the ground plane's normal up_vec_idx, _ = sorted_indexed_eigenvalues[0] up_vec = VBase3(*eigenvectors[:, up_vec_idx]) # Point into the upper half-space if up_vec.z < 0: up_vec *= -1 # Calculate the forward of the centroid centroid_forward = Vec3(0, 1, 0) - up_vec * (Vec3(0, 1, 0).dot(up_vec)) # FIXME: Huh? forward_planar = centroid_forward - up_vec * ( centroid_forward.dot(up_vec)) # Now let's orient and place the centroid self.centroid.set_pos(self.vehicle, (0, 0, 0)) self.centroid.heads_up(forward_planar, up_vec) self.centroid.set_pos(self.vehicle, centroid) # Flight height for repulsor attenuation flight_height = -self.centroid.get_z(self.vehicle) if self.last_flight_height is not None: climb_speed = (flight_height - self.last_flight_height) / globalClock.dt else: climb_speed = 0 self.last_flight_height = flight_height else: local_up = False self.last_flight_height = None flight_height = 0.0 climb_speed = 0.0 # Air drag movement = self.physics_node.get_linear_velocity() drag_coefficient = self.vehicle_data.drag_coefficient drag_area = self.vehicle_data.drag_area + \ self.vehicle_data.airbrake_area * self.airbrake_state + \ self.vehicle_data.stabilizer_fins_area * self.stabilizer_fins_state air_density = self.app.environment.env_data.air_density air_speed = -self.vehicle.get_relative_vector( base.render, movement, ) # drag_force = 1/2*drag_coefficient*mass_density*area*flow_speed**2 result = Vec3(air_speed) result = (result**3) / result.length() result.componentwise_mult(drag_area) result.componentwise_mult(self.vehicle_data.drag_coefficient) drag_force = result * 1 / 2 * air_density self.sensors = { CURRENT_ORIENTATION: self.vehicle.get_hpr(self.app.render), CURRENT_MOVEMENT: movement, CURRENT_ROTATION: self.physics_node.get_angular_velocity(), LOCAL_DRAG_FORCE: drag_force, REPULSOR_DATA: repulsor_data, LOCAL_UP: local_up, FLIGHT_HEIGHT: flight_height, CLIMB_SPEED: climb_speed, } def ecu(self): repulsor_target_angles = self.ecu_repulsor_reorientation() repulsor_activation, delta_height, projected_delta_height, \ power_frac_needed = self.ecu_repulsor_activation() gyro_rotation = self.ecu_gyro_stabilization() thruster_activation = [ self.inputs[THRUST] for _ in self.vehicle_data.thruster_nodes ] airbrake = self.inputs[AIRBRAKE] stabilizer_fins = self.inputs[STABILIZER_FINS] self.commands = { # Steering commands REPULSOR_TARGET_ORIENTATIONS: repulsor_target_angles, REPULSOR_ACTIVATION: repulsor_activation, GYRO_ROTATION: gyro_rotation, THRUSTER_ACTIVATION: thruster_activation, AIRBRAKE: airbrake, STABILIZER_FINS: stabilizer_fins, # ECU data output; Interesting numbers we found along the way. HEIGHT_OVER_TARGET: delta_height, HEIGHT_OVER_TARGET_PROJECTED: projected_delta_height, REPULSOR_POWER_FRACTION_NEEDED: power_frac_needed, } def ecu_repulsor_reorientation(self): # Calculate effective repulsor motion blend values accelerate = self.inputs[ACCELERATE] turn = self.inputs[TURN] strafe = self.inputs[STRAFE] hover = self.inputs[HOVER] length = sum([abs(accelerate), abs(turn), abs(strafe), hover]) if length > 1: accelerate /= length turn /= length strafe /= length hover /= length # Split the turn signal into animation blend factors if turn > 0.0: turn_left = 0.0 turn_right = turn else: turn_left = -turn turn_right = 0.0 # Blend the repulsor angle repulsor_target_angles = [] for node in self.vehicle_data.repulsor_nodes: acc_angle = -(node.get_python_tag(ACCELERATE)) * accelerate turn_left_angle = node.get_python_tag(TURN_LEFT) * turn_left turn_right_angle = node.get_python_tag(TURN_RIGHT) * turn_right strafe_angle = node.get_python_tag(STRAFE) * strafe hover_angle = node.get_python_tag(HOVER) * hover angle = acc_angle + turn_left_angle + turn_right_angle + \ strafe_angle + hover_angle repulsor_target_angles.append(angle) return repulsor_target_angles def ecu_repulsor_activation(self): # Do we know how high we are flying? if self.sensors[LOCAL_UP]: tau = self.inputs[TARGET_FLIGHT_HEIGHT_TAU] # What would we be at in tau seconds if we weren't using repulsors? flight_height = self.sensors[FLIGHT_HEIGHT] target_flight_height = self.inputs[TARGET_FLIGHT_HEIGHT] delta_height = flight_height - target_flight_height gravity_z = self.centroid.get_relative_vector( self.app.render, self.app.environment.physics_world.get_gravity(), ).get_z() # Since gravity is an acceleration gravity_h = 1 / 2 * gravity_z * tau**2 climb_rate = self.sensors[CLIMB_SPEED] * tau projected_delta_height = delta_height + gravity_h + climb_rate # Are we sinking? if projected_delta_height <= 0: # Our projected height will be under our target height, so we # will need to apply the repulsors to make up the difference. # How much climb can each repulsor provide at 100% power right # now? max_powers = [ node.get_python_tag(FORCE) for node in self.vehicle_data.repulsor_nodes ] transferrable_powers = [ max_power * cos(0.5 * pi * data.fraction) for max_power, data in zip( max_powers, self.sensors[REPULSOR_DATA], ) ] angle_ratios = [ cos(node.get_quat(self.vehicle).get_angle_rad()) for node in self.vehicle_data.repulsor_nodes ] angled_powers = [ power * ratio for power, ratio in zip(transferrable_powers, angle_ratios) ] # We don't want to activate the repulsors unevenly, so we'll # have to go by the weakest link. total_angled_power = min(angled_powers) * len(angled_powers) # How high can we climb under 100% repulsor power? max_climb = 1 / 2 * total_angled_power * tau**2 / self.vehicle_data.mass # The fraction of power needed to achieve the desired climb power_frac_needed = -projected_delta_height / max_climb # ...and store it. repulsor_activation = [ power_frac_needed for _ in self.vehicle_data.repulsor_nodes ] else: # We're not sinking. repulsor_activation = [ 0.0 for _ in self.vehicle_data.repulsor_nodes ] power_frac_needed = 0.0 else: # We do not have ground contact. repulsor_activation = [ 0.0 for _ in self.vehicle_data.repulsor_nodes ] delta_height = 0.0 projected_delta_height = 0.0 power_frac_needed = 0.0 # The driver gives 100% repulsor power, no matter how high we are, or # whether we even have ground contact. if self.inputs[FULL_REPULSORS]: repulsor_activation = [ self.inputs[REPULSOR_ACTIVATION] for _ in self.vehicle_data.repulsor_nodes ] return repulsor_activation, delta_height, projected_delta_height,\ power_frac_needed def ecu_gyro_stabilization(self): # Active stabilization and angular dampening # FIXME: Get from self.inputs tau = 0.2 # Seconds until target orientation is reached if self.sensors[LOCAL_UP]: target_mode = self.inputs[ACTIVE_STABILIZATION_ON_GROUND] else: target_mode = self.inputs[ACTIVE_STABILIZATION_IN_AIR] if target_mode == TO_HORIZON: # Stabilize to the current heading, but in a horizontal # orientation self.target_node.set_hpr( self.app.render, self.vehicle.get_h(), 0, 0, ) elif target_mode == TO_GROUND: # Stabilize towards the local up self.target_node.set_hpr(self.centroid, (0, 0, 0)) if target_mode != PASSIVE: xyz_driver_modification = self.inputs[TARGET_ORIENTATION] hpr_driver_modification = VBase3( xyz_driver_modification.z, xyz_driver_modification.x, xyz_driver_modification.y, ) self.target_node.set_hpr( self.target_node, hpr_driver_modification, ) # Now comes the math. orientation = self.vehicle.get_quat(self.app.render) target_orientation = self.target_node.get_quat(self.app.render) delta_orientation = target_orientation * invert(orientation) self.delta_node.set_quat(invert(delta_orientation)) delta_angle = delta_orientation.get_angle_rad() if abs(delta_angle) < (pi / 360 * 0.1) or isnan(delta_angle): delta_angle = 0 axis_of_torque = VBase3(0, 0, 0) else: axis_of_torque = delta_orientation.get_axis() axis_of_torque.normalize() axis_of_torque = self.app.render.get_relative_vector( self.vehicle, axis_of_torque, ) if delta_angle > pi: delta_angle -= 2 * pi # If the mass was standing still, this would be the velocity that # has to be reached to achieve the targeted orientation in tau # seconds. target_angular_velocity = axis_of_torque * delta_angle / tau else: # `elif target_mode == PASSIVE:`, since we might want an OFF mode # Passive stabilization, so this is the pure commanded impulse target_angular_velocity = self.app.render.get_relative_vector( self.vehicle, self.inputs[TARGET_ORIENTATION] * tau / pi, ) # But we also have to cancel out the current velocity for that. angular_velocity = self.physics_node.get_angular_velocity() countering_velocity = -angular_velocity # An impulse of 1 causes an angular velocity of 2.5 rad on a unit mass, # so we have to adjust accordingly. target_impulse = target_angular_velocity / 2.5 * self.vehicle_data.mass countering_impulse = countering_velocity / 2.5 * self.vehicle_data.mass # Now just sum those up, and we have the impulse that needs to be # applied to steer towards target. impulse = target_impulse + countering_impulse return impulse def apply_air_drag(self): drag_force = self.sensors[LOCAL_DRAG_FORCE] global_drag_force = base.render.get_relative_vector( self.vehicle, drag_force, ) if not isnan(global_drag_force.x): self.physics_node.apply_central_impulse( global_drag_force * globalClock.dt, ) def apply_repulsors(self): dt = globalClock.dt repulsor_data = zip(self.vehicle_data.repulsor_nodes, self.sensors[REPULSOR_DATA], self.commands[REPULSOR_ACTIVATION], self.commands[REPULSOR_TARGET_ORIENTATIONS]) for node, data, activation, angle in repulsor_data: active = data.active frac = data.fraction # Repulse in current orientation if active and activation: if activation > 1.0: activation = 1.0 if activation < 0.0: activation = 0.0 # Repulsor power at zero distance base_strength = node.get_python_tag(FORCE) # Effective fraction of repulsors force transfer_frac = cos(0.5 * pi * frac) # Effective repulsor force strength = base_strength * activation * transfer_frac # Resulting impulse impulse_dir = Vec3(0, 0, 1) impulse_dir_world = self.app.render.get_relative_vector( node, impulse_dir, ) impulse = impulse_dir_world * strength # Apply repulsor_pos = node.get_pos(self.vehicle) # FIXME! The position at which an impulse is applied seems to be # centered around node it is applied to, but offset in the world # orientation. So, right distance, wrong angle. This is likely a # bug in Panda3D's Bullet wrapper. Or an idiosyncracy of Bullet. self.physics_node.apply_impulse( impulse * dt, self.app.render.get_relative_vector( self.vehicle, repulsor_pos, ), ) # Contact visualization node max_distance = node.get_python_tag(ACTIVATION_DISTANCE) contact_distance = -impulse_dir_world * max_distance * frac contact_node = node.get_python_tag('ray_end') contact_node.set_pos( node.get_pos(self.app.render) + contact_distance, ) #contact_node.set_hpr(node, 0, -90, 0) # Look towards repulsor #contact_node.set_hpr(0, -90, 0) # Look up contact_node.set_hpr(0, -90, contact_node.getR() + 4) # Look up and rotate contact_node.set_scale(1 - frac) contact_node.show() # Sound sound = node.get_python_tag(REPULSOR_SOUND) sound.set_volume(activation * 20) sound.set_play_rate((1 + activation / 2) * 2) else: node.get_python_tag('ray_end').hide() sound = node.get_python_tag(REPULSOR_SOUND) sound.set_volume(0.0) sound.set_play_rate(0.0) # Reorient old_hpr = node.get_python_tag(REPULSOR_OLD_ORIENTATION) want_hpr = VBase3(angle.z, angle.x, angle.y) delta_hpr = want_hpr - old_hpr max_angle = node.get_python_tag(REPULSOR_TURNING_ANGLE) * dt if delta_hpr.length() > max_angle: delta_hpr = delta_hpr / delta_hpr.length() * max_angle new_hpr = old_hpr + delta_hpr node.set_hpr(new_hpr) node.set_python_tag(REPULSOR_OLD_ORIENTATION, new_hpr) def apply_gyroscope(self): impulse = self.commands[GYRO_ROTATION] # Clamp the impulse to what the "motor" can produce. max_impulse = self.vehicle_data.max_gyro_torque if impulse.length() > max_impulse: clamped_impulse = impulse / impulse.length() * max_impulse else: clamped_impulse = impulse self.physics_node.apply_torque_impulse(clamped_impulse) impulse_ratio = clamped_impulse.length() / max_impulse sound = self.model.get_python_tag(GYROSCOPE_SOUND) sound.set_volume(0.5 * impulse_ratio) sound.set_play_rate(0.9 + 0.1 * impulse_ratio) def apply_thrusters(self): dt = globalClock.dt thruster_data = zip( self.vehicle_data.thruster_nodes, self.commands[THRUSTER_ACTIVATION], ) for node, thrust in thruster_data: if self.thruster_heat >= 1.0: thrust = 0.0 current_power = node.get_python_tag(THRUSTER_POWER) # Adjust thrust to be within bounds of thruster's ability to adjust # thrust. ramp_time = node.get_python_tag(THRUSTER_RAMP_TIME) ramp_step = (1 / ramp_time) * globalClock.dt thrust = adjust_within_limit(thrust, current_power, ramp_step) node.set_python_tag(THRUSTER_POWER, thrust) max_force = node.get_python_tag(FORCE) real_force = max_force * thrust # FIXME: See repulsors above for the shortcoming that this suffers thruster_pos = base.render.get_relative_vector( self.vehicle, node.get_pos(self.vehicle), ) thrust_direction = self.app.render.get_relative_vector( node, Vec3(0, 0, 1)) self.physics_node.apply_impulse( thrust_direction * real_force * dt, thruster_pos, ) heating = node.get_python_tag(THRUSTER_HEATING) cooling = node.get_python_tag(THRUSTER_COOLING) effective_heating = -cooling + (heating - cooling) * thrust self.thruster_heat += effective_heating * dt if self.thruster_heat < 0.0: self.thruster_heat = 0.0 # Sound sound = node.get_python_tag(THRUSTER_SOUND) sound.set_volume(thrust * 5) sound.set_play_rate((1 + thrust / 20) / 3) was_overheated = node.get_python_tag(THRUSTER_OVERHEATED) is_overheated = self.thruster_heat > 1.0 if is_overheated and not was_overheated: sound = node.get_python_tag(THRUSTER_OVERHEAT_SOUND) sound.set_volume(5) sound.play() node.set_python_tag(THRUSTER_OVERHEATED, True) if not is_overheated: node.set_python_tag(THRUSTER_OVERHEATED, False) def apply_airbrake(self): # Animation and state update only, since the effect is in air drag dt = globalClock.dt target = self.commands[AIRBRAKE] max_movement = self.airbrake_speed * dt # Clamp change to available speed delta = target - self.airbrake_state if abs(delta) > max_movement: self.airbrake_state += copysign(max_movement, delta) else: self.airbrake_state = target if self.airbrake_state > 1.0: self.airbrake_state = 1.0 if self.airbrake_state < 0.0: self.airbrake_state = 0.0 #self.model.pose(AIRBRAKE, self.airbrake_state, partName=AIRBRAKE) self.model.pose(AIRBRAKE, self.airbrake_state) def apply_stabilizer_fins(self): # Animation and state update only, since the effect is in air drag dt = globalClock.dt target = self.commands[STABILIZER_FINS] max_movement = self.stabilizer_fins_speed * dt # Clamp change to available speed delta = target - self.stabilizer_fins_state if abs(delta) > max_movement: self.stabilizer_fins_state += copysign(max_movement, delta) else: self.stabilizer_fins_state = target if self.stabilizer_fins_state > 1.0: self.stabilizer_fins_state = 1.0 if self.stabilizer_fins_state < 0.0: self.stabilizer_fins_state = 0.0 self.model.pose( STABILIZER_FINS, self.stabilizer_fins_state, #partName=STABILIZER_FINS, ) # FIXME: Implement stabilizing effect def shock(self, x=0, y=0, z=0): self.physics_node.apply_impulse( Vec3(0, 0, 0), Vec3(random(), random(), random()) * 10, ) self.physics_node.apply_torque_impulse(Vec3(x, y, z))
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()
def main(): ECSShowBase() base.disable_mouse() base.accept('escape', sys.exit) base.cam.set_pos(4, -5, 2) base.cam.look_at(0, 0, 0) system_types = [ prototype.ManageModels, clock.DetermineTimestep, prototype.DeterminePhysicsTimestep, prototype.DoPhysics, ] for sort, system_type in enumerate(system_types): base.add_system(system_type(), sort) # Bullet world bullet_world = BulletWorld() bullet_world.set_gravity(Vec3(0, 0, -9.81)) if debug: debugNode = BulletDebugNode('Debug') debugNode.showWireframe(True) debugNode.showConstraints(True) debugNode.showBoundingBoxes(False) debugNode.showNormals(False) debugNP = render.attachNewNode(debugNode) debugNP.show() bullet_world.setDebugNode(debugNP.node()) world = base.ecs_world.create_entity( clock.Clock(clock=clock.panda3d_clock), prototype.PhysicsWorld(world=bullet_world, timestep=1 / 30), ) base.ecs_world._flush_component_updates() # Something for the models to fall on bullet_body = BulletRigidBodyNode() bullet_body.set_mass(0.0) bullet_body.add_shape( BulletBoxShape(Vec3(base_size, base_size, 0.1)), TransformState.makePos(Point3(0, 0, 0)), ) bullet_body.add_shape( BulletBoxShape(Vec3(base_size, 0.1, wall_height)), TransformState.makePos(Point3(0, -base_size, wall_height)), ) bullet_body.add_shape( BulletBoxShape(Vec3(base_size, 0.1, wall_height)), TransformState.makePos(Point3(0, base_size, wall_height)), ) bullet_body.add_shape( BulletBoxShape(Vec3(0.1, base_size, wall_height)), TransformState.makePos(Point3(-base_size, 0, wall_height)), ) bullet_body.add_shape( BulletBoxShape(Vec3(0.1, base_size, wall_height)), TransformState.makePos(Point3(base_size, 0, wall_height)), ) base.ecs_world.create_entity( prototype.Model(post_attach=prototype.transform(pos=Vec3(0, 0, -1)), ), prototype.PhysicsBody( body=bullet_body, world=world._uid, ), ) # Regularly add a ball def add_ball(task): bullet_body = BulletRigidBodyNode() bullet_body.set_linear_sleep_threshold(0) bullet_body.set_angular_sleep_threshold(0) bullet_body.set_mass(1.0) bullet_body.add_shape(BulletSphereShape(ball_size)) func = random.choice([ add_smiley, add_panda, add_mr_man_static, add_mr_man_dynamic, ]) func(world, bullet_body) return task.again base.do_method_later(creation_interval, add_ball, 'add ball') # Start base.run()
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape(BulletPlaneShape(Vec3(0, 1, 0), 1)) return node
debug_node.showWireframe(True) debug_node.showConstraints(True) 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)
def create_solid(self): node = BulletRigidBodyNode(self.name) mesh = BulletConvexHullShape() mesh.add_geom(self.geom.get_geom(0)) node.add_shape(mesh) return node
def create_solid(self): node = BulletRigidBodyNode(self.name) node.add_shape(BulletPlaneShape(Vec3(0, 1, 0), 1)) return node