def test_sphere_into_heightfield(): root = NodePath("root") world = BulletWorld() # Create PNMImage to construct Heightfield with img = PNMImage(10, 10, 1) img.fill_val(255) # Make our nodes heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp) sphere = make_node("Sphere", BulletSphereShape, 1) # Attach to world np1 = root.attach_new_node(sphere) np1.set_pos(0, 0, 1) world.attach(sphere) np2 = root.attach_new_node(heightfield) np2.set_pos(0, 0, 0) world.attach(heightfield) assert world.get_num_rigid_bodies() == 2 test = world.contact_test_pair(sphere, heightfield) assert test.get_num_contacts() > 0 assert test.get_contact(0).get_node0() == sphere assert test.get_contact(0).get_node1() == heightfield # Increment sphere's Z coordinate, no longer colliding np1.set_pos(0, 0, 2) test = world.contact_test_pair(sphere, heightfield) assert test.get_num_contacts() == 0
def bulletRayHit(pfrom, pto, geom): """ NOTE: this function is quite slow find the nearest collision point between vec(pto-pfrom) and the mesh of nodepath :param pfrom: starting point of the ray, Point3 :param pto: ending point of the ray, Point3 :param geom: meshmodel, a panda3d datatype :return: None or Point3 author: weiwei date: 20161201 """ bulletworld = BulletWorld() facetmesh = BulletTriangleMesh() facetmesh.addGeom(geom) facetmeshnode = BulletRigidBodyNode('facet') bullettmshape = BulletTriangleMeshShape(facetmesh, dynamic=True) bullettmshape.setMargin(1e-6) facetmeshnode.addShape(bullettmshape) bulletworld.attach(facetmeshnode) result = bulletworld.rayTestClosest(pfrom, pto) if result.hasHit(): return result.getHitPos() else: return None
def attach_to_physics_world(self, bullet_world: BulletWorld): """ Attach the nodes in this list to bullet world :param bullet_world: BulletWorld() :return: None """ if self.attached: return for node in self: bullet_world.attach(node) self.attached = True
def test_get_steering(): world = BulletWorld() # Chassis shape = BulletBoxShape(Vec3(0.6, 1.4, 0.5)) body = BulletRigidBodyNode('Vehicle') body.addShape(shape) world.attach(body) # Vehicle vehicle = BulletVehicle(world, body) world.attachVehicle(vehicle) # Wheel wheel = vehicle.createWheel() wheel.setSteering(30.0) # Returns the steering angle in degrees. assert wheel.getSteering() == approx(30.0)
class TestApplication(ShowBase): def __init__(self): ShowBase.__init__(self) # game objects self.controlled_obj_index_ = 0 self.level_sectors_ = [] self.controlled_objects_ = [] # active objects self.active_sector_ = None self.setupRendering() self.setupControls() self.setupPhysics() self.clock_ = ClockObject() self.kinematic_mode_ = False # Task logging.info("TestSectors demo started") taskMgr.add(self.update, 'updateWorld') def setupRendering(self): self.setBackgroundColor(0.1, 0.1, 0.8, 1) self.setFrameRateMeter(True) # Light alight = AmbientLight('ambientLight') alight.setColor(Vec4(0.5, 0.5, 0.5, 1)) alightNP = self.render.attachNewNode(alight) dlight = DirectionalLight('directionalLight') dlight.setDirection(Vec3(1, 1, -1)) dlight.setColor(Vec4(0.7, 0.7, 0.7, 1)) dlightNP = self.render.attachNewNode(dlight) self.render.clearLight() self.render.setLight(alightNP) self.render.setLight(dlightNP) self.setupBackgroundImage() def setupBackgroundImage(self): image_file = Filename(rospack.RosPack().get_path(BACKGROUND_IMAGE_PACKAGE) + '/resources/backgrounds/' + BACKGROUND_IMAGE_PATH) # check if image can be loaded img_head = PNMImageHeader() if not img_head.readHeader(image_file ): raise IOError, "PNMImageHeader could not read file %s. Try using absolute filepaths"%(image_file.c_str()) sys.exit() # Load the image with a PNMImage w = img_head.getXSize() h = img_head.getYSize() img = PNMImage(w,h) #img.alphaFill(0) img.read(image_file) texture = Texture() texture.setXSize(w) texture.setYSize(h) texture.setZSize(1) texture.load(img) texture.setWrapU(Texture.WM_border_color) # gets rid of odd black edges around image texture.setWrapV(Texture.WM_border_color) texture.setBorderColor(LColor(0,0,0,0)) # creating CardMaker to hold the texture cm = CardMaker('background') cm.setFrame(-0.5*w,0.5*w,-0.5*h,0.5*h) # This configuration places the image's topleft corner at the origin (left, right, bottom, top) background_np = NodePath(cm.generate()) background_np.setTexture(texture) background_np.reparentTo(self.render) background_np.setPos(BACKGROUND_POSITION) background_np.setScale(BACKGROUND_IMAGE_SCALE) def setupControls(self): # Input (Events) self.accept('escape', self.doExit) self.accept('r', self.doReset) self.accept('f1', self.toggleWireframe) self.accept('f2', self.toggleTexture) self.accept('f3', self.toggleDebug) self.accept('f5', self.doScreenshot) self.accept('n', self.selectNextControlledObject) self.accept('k', self.toggleKinematicMode) # Inputs (Polling) self.input_state_ = InputState() self.input_state_.watchWithModifiers("right","arrow_right") self.input_state_.watchWithModifiers('left', 'arrow_left') self.input_state_.watchWithModifiers('jump', 'arrow_up') self.input_state_.watchWithModifiers('stop', 'arrow_down') self.input_state_.watchWithModifiers('roll_right', 'c') self.input_state_.watchWithModifiers('roll_left', 'z') self.input_state_.watchWithModifiers('roll_stop', 'x') self.title = addTitle("Panda3D: Kinematic Objects") self.inst1 = addInstructions(0.06, "ESC: Quit") self.inst2 = addInstructions(0.12, "Up/Down: Jump/Stop") self.inst3 = addInstructions(0.18, "Left/Right: Move Left / Move Rigth") self.inst4 = addInstructions(0.24, "z/x/c : Rotate Left/ Rotate Stop / Rotate Right") self.inst5 = addInstructions(0.30, "n: Select Next Character") self.inst6 = addInstructions(0.36, "k: Toggle Kinematic Mode") self.inst7 = addInstructions(0.42, "f1: Toggle Wireframe") self.inst8 = addInstructions(0.48, "f2: Toggle Texture") self.inst9 = addInstructions(0.54, "f3: Toggle BulletDebug") self.inst10 = addInstructions(0.60, "f5: Capture Screenshot") def processCollisions(self): contact_manifolds = self.physics_world_.getManifolds() for cm in contact_manifolds: node0 = cm.getNode0() node1 = cm.getNode1() col_mask1 = node0.getIntoCollideMask() col_mask2 = node1.getIntoCollideMask() if (col_mask1 == col_mask2) and \ (col_mask1 == SECTOR_ENTERED_BITMASK) and \ self.active_sector_.getSectorPlaneNode().getName() != node0.getName(): logging.info('Collision between %s and %s'%(node0.getName(),node1.getName())) sector = [s for s in self.level_sectors_ if s.getSectorPlaneNode().getName() == node0.getName()] sector = [s for s in self.level_sectors_ if s.getSectorPlaneNode().getName() == node1.getName()] + sector self.switchToSector(sector[0]) logging.info("Sector %s entered"%(self.active_sector_.getName())) def switchToSector(self,sector): obj_index = self.controlled_obj_index_ character_obj = self.controlled_objects_[obj_index] if self.active_sector_ is not None: self.active_sector_.releaseCharacter() character_obj.setY(sector,0) character_obj.setH(sector,0) self.active_sector_ = sector self.active_sector_.constrainCharacter(character_obj) self.active_sector_.enableDetection(False) sectors = [s for s in self.level_sectors_ if s != self.active_sector_] for s in sectors: s.enableDetection(True) def setupPhysics(self): # setting up physics world and parent node path self.physics_world_ = BulletWorld() self.world_node_ = self.render.attachNewNode('world') self.cam.reparentTo(self.world_node_) self.physics_world_.setGravity(Vec3(0, 0, -9.81)) self.debug_node_ = self.world_node_.attachNewNode(BulletDebugNode('Debug')) self.debug_node_.node().showWireframe(True) self.debug_node_.node().showConstraints(True) self.debug_node_.node().showBoundingBoxes(True) self.debug_node_.node().showNormals(True) self.physics_world_.setDebugNode(self.debug_node_.node()) self.debug_node_.hide() # setting up collision groups self.physics_world_.setGroupCollisionFlag(GAME_OBJECT_BITMASK.getLowestOnBit(),GAME_OBJECT_BITMASK.getLowestOnBit(),True) self.physics_world_.setGroupCollisionFlag(SECTOR_ENTERED_BITMASK.getLowestOnBit(),SECTOR_ENTERED_BITMASK.getLowestOnBit(),True) self.physics_world_.setGroupCollisionFlag(GAME_OBJECT_BITMASK.getLowestOnBit(),SECTOR_ENTERED_BITMASK.getLowestOnBit(),False) # setting up ground self.ground_ = self.world_node_.attachNewNode(BulletRigidBodyNode('Ground')) self.ground_.node().addShape(BulletPlaneShape(Vec3(0, 0, 1), 0)) self.ground_.setPos(0,0,0) self.ground_.setCollideMask(GAME_OBJECT_BITMASK) self.physics_world_.attachRigidBody(self.ground_.node()) # creating level objects self.setupLevelSectors() # creating controlled objects diameter = 0.4 sphere_visual = loader.loadModel('models/ball.egg') bounds = sphere_visual.getTightBounds() # start of model scaling extents = Vec3(bounds[1] - bounds[0]) scale_factor = diameter/max([extents.getX(),extents.getY(),extents.getZ()]) sphere_visual.clearModelNodes() sphere_visual.setScale(scale_factor,scale_factor,scale_factor) # end of model scaling sphere_visual.setTexture(loader.loadTexture('models/bowl.jpg'),1) sphere = NodePath(BulletRigidBodyNode('Sphere')) sphere.node().addShape(BulletSphereShape(0.5*diameter)) sphere.node().setMass(1.0) #sphere.node().setLinearFactor((1,0,1)) #sphere.node().setAngularFactor((0,1,0)) sphere.setCollideMask(GAME_OBJECT_BITMASK) sphere_visual.instanceTo(sphere) self.physics_world_.attachRigidBody(sphere.node()) self.controlled_objects_.append(sphere) sphere.reparentTo(self.world_node_) sphere.setPos(self.level_sectors_[0],Vec3(0,0,diameter+10)) sphere.setHpr(self.level_sectors_[0],Vec3(0,0,0)) # creating bullet ghost for detecting entry into other sectors player_ghost = sphere.attachNewNode(BulletGhostNode('player-ghost-node')) ghost_box_shape = BulletSphereShape(PLAYER_GHOST_DIAMETER/2) ghost_box_shape.setMargin(SECTOR_COLLISION_MARGIN) ghost_sphere_shape = BulletSphereShape(PLAYER_GHOST_DIAMETER*0.5) ghost_sphere_shape.setMargin(SECTOR_COLLISION_MARGIN) player_ghost.node().addShape(ghost_box_shape) player_ghost.setPos(sphere,Vec3(0,0,0)) player_ghost.node().setIntoCollideMask(SECTOR_ENTERED_BITMASK) self.physics_world_.attach(player_ghost.node()) # creating mobile box size = Vec3(0.2,0.2,0.4) mbox_visual = loader.loadModel('models/box.egg') bounds = mbox_visual.getTightBounds() extents = Vec3(bounds[1] - bounds[0]) scale_factor = 1/max([extents.getX(),extents.getY(),extents.getZ()]) mbox_visual.setScale(size.getX()*scale_factor,size.getY()*scale_factor,size.getZ()*scale_factor) mbox = NodePath(BulletRigidBodyNode('MobileBox')) mbox.node().addShape(BulletBoxShape(size/2)) mbox.node().setMass(1.0) #mbox.node().setLinearFactor((1,0,1)) #mbox.node().setAngularFactor((0,1,0)) mbox.setCollideMask(GAME_OBJECT_BITMASK) mbox_visual.instanceTo(mbox) self.physics_world_.attachRigidBody(mbox.node()) self.controlled_objects_.append(mbox) mbox.reparentTo(self.level_sectors_[0]) mbox.setPos(Vec3(1,0,size.getZ()+1)) # switching to sector 1 self.switchToSector(self.level_sectors_[0]) def setupLevelSectors(self): start_pos = Vec3(0,-20,0) for i in range(0,4): sector = Sector('sector' + str(i),self.physics_world_) sector.reparentTo(self.world_node_) #sector.setHpr(Vec3(60*(i+1),0,0)) sector.setHpr(Vec3(90*i,0,0)) sector.setPos(sector,Vec3(0,-20,i*4)) self.level_sectors_.append(sector) sector.setup() def update(self, task): self.clock_.tick() dt = self.clock_.getDt() self.processInput(dt) self.physics_world_.doPhysics(dt, 5, 1.0/180.0) self.processCollisions() self.updateCamera() return task.cont def processInput(self,dt): activate = False obj = self.controlled_objects_[self.controlled_obj_index_] if self.kinematic_mode_: obj.node().setKinematic(True) return else: obj.node().setKinematic(False) vel = self.active_sector_.getRelativeVector(self.world_node_,obj.node().getLinearVelocity()) w = self.active_sector_.getRelativeVector(self.world_node_, obj.node().getAngularVelocity()) vel.setY(0) #w.setX(0) #w.setZ(0) if self.input_state_.isSet('right'): vel.setX(2) activate = True if self.input_state_.isSet('left'): vel.setX(-2) activate = True if self.input_state_.isSet('jump'): vel.setZ(4) activate = True if self.input_state_.isSet('stop'): vel.setX(0) if self.input_state_.isSet('roll_right'): w.setY(ROTATIONAl_SPEED) activate = True if self.input_state_.isSet('roll_left'): w.setY(-ROTATIONAl_SPEED) activate = True if self.input_state_.isSet('roll_stop'): w.setY(0) if activate : obj.node().setActive(True,True) vel = self.world_node_.getRelativeVector(self.active_sector_,vel) w = self.world_node_.getRelativeVector(self.active_sector_,w) obj.node().setLinearVelocity(vel) obj.node().setAngularVelocity(w) #logging.info("Linear Velocity: %s"%(str(vel))) def updateCamera(self): if len(self.controlled_objects_) > 0: obj = self.controlled_objects_[self.controlled_obj_index_] self.cam.setPos(self.active_sector_,obj.getPos(self.active_sector_)) self.cam.setY(self.active_sector_,-CAM_DISTANCE/1) self.cam.lookAt(obj.getParent(),obj.getPos()) def cleanup(self): for i in range(0,len(self.controlled_objects_)): rb = self.controlled_objects_[i] self.physics_world_.removeRigidBody(rb.node()) rb.removeNode() self.object_nodes_ = [] self.controlled_objects_ = [] self.physics_world_.removeRigidBody(self.ground_.node()) self.ground_ = None self.physics_world_ = None self.debug_node_ = None self.cam.reparentTo(self.render) self.world_node_.removeNode() self.world_node_ = None # _____HANDLER_____ def toggleKinematicMode(self): self.kinematic_mode_ = not self.kinematic_mode_ print "Kinematic Mode %s"%('ON' if self.kinematic_mode_ else 'OFF') def selectNextControlledObject(self): self.controlled_obj_index_+=1 if self.controlled_obj_index_ >= len(self.controlled_objects_): self.controlled_obj_index_ = 0 # reset def doExit(self): self.cleanup() sys.exit(1) def doReset(self): self.cleanup() self.setupPhysics() def toggleDebug(self): if self.debug_node_.isHidden(): self.debug_node_.show() else: self.debug_node_.hide() def doScreenshot(self): self.screenshot('Bullet')
class BCMchecker(object): """ BBCMchecker bullet box collision model checker """ def __init__(self, toggledebug=False): self.world = BulletWorld() self._toggledebug = toggledebug if self._toggledebug: bulletcolliderrender = base.render.attachNewNode( "bulletboxcollider") debugNode = BulletDebugNode('Debug') debugNode.showWireframe(True) debugNode.showConstraints(True) debugNode.showBoundingBoxes(False) debugNode.showNormals(False) self._debugNP = bulletcolliderrender.attachNewNode(debugNode) self.world.setDebugNode(self._debugNP.node()) self.worldrigidbodylist = [] self._isupdateworldadded = False def _updateworld(self, world, task): world.doPhysics(globalClock.getDt()) return task.cont def isBoxBoxCollided(self, objcm0, objcm1): """ check if two objects objcm0 as objcm1 are in collision with each other the two objects are in the form of collision model the AABB boxlist will be used type "box" is required :param objcm0: the first object :param objcm1: the second object :return: boolean value showing if the two objects are in collision author: weiwei date: 20190313 """ objcm0boxbullnode = genBulletCDBox(objcm0) objcm1boxbullnode = genBulletCDBox(objcm1) result = self.world.contactTestPair(objcm0boxbullnode, objcm1boxbullnode) if not result.getNumContacts(): return False else: return True def isBoxBoxListCollided(self, objcm0, objcmlist=[]): """ check if objcm0 is in collision with a list of collisionmodels in objcmlist each obj is in the form of a collision model :param objcm0: :param obcmjlist: a list of collision models :return: boolean value showing if the object and the list are in collision author: weiwei date: 20190313 """ objcm0boxbullnode = genBulletCDBox(objcm0) objcm1boxbullnode = genBulletCDBoxList(objcmlist) result = self.world.contactTestPair(objcm0boxbullnode, objcm1boxbullnode) if not result.getNumContacts(): return False else: return True def isBoxListBoxListCollided(self, objcm0list=[], objcm1list=[]): """ check if a list of collisionmodels in objcm0list is in collision with a list of collisionmodels in objcm1list each obj is in the form of a collision model :param objcm0list: a list of collision models :param obcmj1list: a list of collision models :return: boolean value showing if the object and the list are in collision author: weiwei date: 20190313 """ objcm0boxbullnode = genBulletCDBoxList(objcm0list) objcm1boxbullnode = genBulletCDBoxList(objcm1list) result = self.world.contactTestPair(objcm0boxbullnode, objcm1boxbullnode) if not result.getNumContacts(): return False else: return True def showBox(self, objcm): """ show the AABB collision meshes of the given objects :param objcm author: weiwei date: 20190313 :return: """ if not self._toggledebug: print( "Toggle debug on during defining the XCMchecker object to use showfunctions!" ) return if not self._isupdateworldadded: base.taskMgr.add(self._updateworld, "updateworld", extraArgs=[self.world], appendTask=True) objcmboxbullnode = genBulletCDBox(objcm) self.world.attach(objcmboxbullnode) self.worldrigidbodylist.append(objcmboxbullnode) self._debugNP.show() def showBoxList(self, objcmlist): """ show the AABB collision meshes of the given objects :param objcm0, objcm1 author: weiwei date: 20190313 :return: """ if not self._toggledebug: print( "Toggle debug on during defining the XCMchecker object to use showfunctions!" ) return if not self._isupdateworldadded: base.taskMgr.add(self._updateworld, "updateworld", extraArgs=[self.world], appendTask=True) objcmboxbullnode = genBulletCDBoxList(objcmlist) self.world.attach(objcmboxbullnode) self.worldrigidbodylist.append(objcmboxbullnode) self._debugNP.show() def unshow(self): """ unshow everything author: weiwei date: 20180621 :return: """ base.taskMgr.remove("updateworld") print(self.worldrigidbodylist) for cdnode in self.worldrigidbodylist: self.world.remove(cdnode) self.worldrigidbodylist = [] self._debugNP.hide()
class Game(ShowBase): def __init__(self): """ Load some configuration variables, it's important for this to happen before the ShowBase is initialized """ load_prc_file_data( "", """ sync-video #t ### add entries below if you are not running from an installation. #model-path /path/to/panda3d """) ShowBase.__init__(self) base.set_background_color(0.1, 0.1, 0.8, 1) base.set_frame_rate_meter(True) base.cam.set_pos(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) self.accept('space', self.do_jump) self.accept('c', self.do_crouch) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') def do_jump(self): self.character.set_max_jump_height(5.0) self.character.set_jump_speed(8.0) self.character.do_jump() self.actorNP.play("jump") def do_crouch(self): self.crouching = not self.crouching sz = self.crouching and 0.6 or 1.0 self.characterNP.set_scale(LVector3(1, 1, sz)) #self.character.get_shape().set_local_scale(LVector3(1, 1, sz)) #self.characterNP.set_scale(LVector3(1, 1, sz) * 0.3048) #self.characterNP.set_pos(0, 0, -1 * sz) # ____TASK___ def process_input(self, dt): speed = LVector3(0, 0, 0) omega = 0.0 if inputState.isSet('forward'): speed.y = 2.0 if inputState.isSet('reverse'): speed.y = -2.0 if inputState.isSet('left'): speed.x = -2.0 if inputState.isSet('right'): speed.x = 2.0 if inputState.isSet('turnLeft'): omega = 120.0 if inputState.isSet('turnRight'): omega = -120.0 self.character.set_angular_movement(omega) self.character.set_linear_movement(speed, True) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt, 4, 1. / 240.) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), 0) #img = PNMImage(Filename('models/elevation.png')) #shape = BulletHeightfieldShape(img, 1.0, ZUp) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Box shape = BulletBoxShape(LVector3(1.0, 3.0, 0.3)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box')) np.node().set_mass(10.0) np.node().add_shape(shape) np.set_pos(3, 0, 4) np.setH(20.0) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Character self.crouching = False h = 1.75 w = 0.4 shape = BulletCapsuleShape(w, h - 2 * w, ZUp) self.character = BulletCharacterControllerNode(shape, 0.4, 'Player') self.characterNP = self.worldNP.attach_new_node(self.character) self.characterNP.set_pos(-2, 0, 14) self.characterNP.set_h(45) self.characterNP.set_collide_mask(BitMask32.all_on()) self.world.attach(self.character) self.actorNP = Actor( 'samples/roaming-ralph/models/ralph.egg.pz', { 'run': 'samples/roaming-ralph/models/ralph-run.egg.pz', 'walk': 'samples/roaming-ralph/models/ralph-walk.egg.pz' }) self.actorNP.reparent_to(self.characterNP) self.actorNP.set_scale(0.3048) # 1ft = 0.3048m self.actorNP.setH(180) self.actorNP.set_pos(0, 0, -1)
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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 self.boxNP.node().set_active(True) self.boxNP.node().apply_central_force(force) self.boxNP.node().apply_torque(torque) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), -1) mask = BitMask32(0x0f) print('ground: ', mask) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_collide_mask(mask) self.world.attach(np.node()) ## Box 1 #shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) #mask = BitMask32.allOff() #print('box-1: ', mask) #np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-1')) #np.node().set_mass(1.0) #np.node().add_shape(shape) #np.set_pos(-6, 0, 4) #np.set_collide_mask(mask) #self.world.attach(np.node()) ## Box 2 #shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) #mask = BitMask32.bit(0) #print('box-2: ', mask) #np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-2')) #np.node().set_mass(1.0) #np.node().add_shape(shape) #np.set_pos(-2, 0, 4) #np.set_collide_mask(mask) #self.world.attach(np.node()) ## Box 3 #shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) #mask = BitMask32.bit(2) #print('box-3: ', mask) #np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-3')) #np.node().set_mass(1.0) #np.node().add_shape(shape) #np.set_pos(2, 0, 4) #np.set_collide_mask(mask) #self.world.attach(np.node()) # Box 4 shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) mask = BitMask32(0x3) print('box-4: ', mask) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-4')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(6, 0, 4) np.set_collide_mask(mask) self.world.attach(np.node()) self.boxNP = np visualNP = loader.load_model('models/box.egg') visualNP.reparent_to(self.boxNP)
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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) self.accept('1', self.do_select, [ 0, ]) self.accept('2', self.do_select, [ 1, ]) self.accept('3', self.do_select, [ 2, ]) self.accept('4', self.do_select, [ 3, ]) self.accept('5', self.do_select, [ 4, ]) self.accept('6', self.do_select, [ 5, ]) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') def do_select(self, i): self.boxNP = self.boxes[i] # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 self.boxNP.node().set_active(True) self.boxNP.node().apply_central_force(force) self.boxNP.node().apply_torque(torque) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), -1) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_collide_mask(BitMask32.bit(0)) self.world.attach(np.node()) # Boxes self.boxes = [ None, ] * 6 for i in range(6): shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-1')) np.node().set_mass(1.0) np.node().add_shape(shape) self.world.attach(np.node()) self.boxes[i] = np visualNP = loader.load_model('models/box.egg') visualNP.reparent_to(np) self.boxes[0].set_pos(-3, -3, 0) self.boxes[1].set_pos(0, -3, 0) self.boxes[2].set_pos(3, -3, 0) self.boxes[3].set_pos(-3, 3, 0) self.boxes[4].set_pos(0, 3, 0) self.boxes[5].set_pos(3, 3, 0) self.boxes[0].set_collide_mask(BitMask32.bit(1)) self.boxes[1].set_collide_mask(BitMask32.bit(2)) self.boxes[2].set_collide_mask(BitMask32.bit(3)) self.boxes[3].set_collide_mask(BitMask32.bit(1)) self.boxes[4].set_collide_mask(BitMask32.bit(2)) self.boxes[5].set_collide_mask(BitMask32.bit(3)) self.boxNP = self.boxes[0] self.world.set_group_collision_flag(0, 1, True) self.world.set_group_collision_flag(0, 2, True) self.world.set_group_collision_flag(0, 3, True) self.world.set_group_collision_flag(1, 1, False) self.world.set_group_collision_flag(1, 2, True)
class BulletBase(object): """ Manages Panda3d Bullet physics resources and convenience methods.""" # Bitmasks for each object type. By setting ghost and static # objects to different masks, we can filter ghost-to-static # collisions. ghost_bit = BitMask32.bit(1) static_bit = BitMask32.bit(2) dynamic_bit = ghost_bit | static_bit bw_types = (BulletBaseCharacterControllerNode, BulletBodyNode, BulletConstraint, BulletVehicle) def __init__(self): self.world = None # Parameters for simulation. self.sim_par = {"size": 1. / 100, "n_subs": 10, "size_sub": 1. / 1000} # Initialize axis constraint so that there aren't constraints. self.axis_constraint_fac = Vec3(1, 1, 1) self.axis_constraint_disp = Vec3(nan, nan, nan) # Attribute names of destructable objects. self._destructables = () def init(self): """ Initialize world and resources. """ # Initialize world. self.world = BulletWorld() def destroy(self): """ Destroy all resources.""" for key in self._destructables: getattr(self, key).destroy() def setup_debug(self): debug_node = BulletDebugNode('Debug') debug_node.showWireframe(True) debug_node.showConstraints(True) debug_node.showBoundingBoxes(True) debug_node.showNormals(True) self.world.setDebugNode(debug_node) return debug_node @property def bodies(self): """ Return all bodies (rigid, soft, and ghost) in self.world.""" bodies = (self.world.getRigidBodies() + self.world.getSoftBodies() + self.world.getGhosts()) return bodies def _constrain_axis(self, body): """ Apply existing axis constraints to a body.""" # Set displacements. for axis, (f, d) in enumerate(zip(self.axis_constraint_fac, self.axis_constraint_disp)): if not f and not isnan(d): nodep = NodePath(body) pos = nodep.getPos() pos[axis] = d nodep.setPos(pos) try: # Linear and angular factors of 0 mean forces in the # corresponding axis are scaled to 0. body.setLinearFactor(self.axis_constraint_fac) # Angular factors restrict rotation about an axis, so the # following logic selects the appropriate axes to # constrain. s = sum(self.axis_constraint_fac) if s == 3.: v = self.axis_constraint_fac elif s == 2.: v = -self.axis_constraint_fac + 1 else: v = Vec3.zero() body.setAngularFactor(v) except AttributeError: # The body was not a rigid body (it didn't have # setLinearFactor method). pass def set_axis_constraint(self, axis, on, disp=None): """ Sets an axis constraint, so that bodies can/cannot move in that direction.""" # Create the constraint vector. self.axis_constraint_fac[axis] = int(not on) self.axis_constraint_disp[axis] = disp if disp is not None else nan # Iterate over bodies, applying the constraint. for body in self.bodies: self._constrain_axis(body) def attach(self, objs, suppress_deact_warn=False): """ Attach Bullet objects to the world.""" if not self.world: raise BulletBaseError("No BulletWorld initialized.") # Make sure they're iterable. if not isinstance(objs, Iterable): objs = [objs] elif isinstance(objs, dict): objs = objs.itervalues() bw_objs = [] for obj in objs: if isinstance(obj, NodePath): obj = obj.node() if isinstance(obj, self.bw_types): bw_objs.append(obj) # Don't attach ones that are already attached. bw_objs = set(bw_objs) - set(self.bodies) # Attach them. for obj in bw_objs: # Warn about deactivation being enabled. if (not suppress_deact_warn and getattr(obj, "isDeactivationEnabled", lambda: True)()): msg = "Deactivation is enabled on object: %s" % obj warn(msg, DeactivationEnabledWarning) # Apply existing axis constraints to the objects. self._constrain_axis(obj) # Attach the objects to the world. try: self.world.attach(obj) except AttributeError: DeprecationWarning("Upgrade to Panda3d 1.9.") for attr in ("attachRigidBody", "attachConstraint", "attachGhost"): attach = getattr(self.world, attr) try: attach(obj) except TypeError: pass else: break def remove(self, objs): """ Remove Bullet objects to the world.""" if not self.world: raise BulletBaseError("No BulletWorld initialized.") # Make sure they're iterable. if not isinstance(objs, Iterable): objs = [objs] elif isinstance(objs, dict): objs = objs.itervalues() bw_objs = [] for obj in objs: if isinstance(obj, NodePath): obj = obj.node() if isinstance(obj, self.bw_types): bw_objs.append(obj) # Remove them. for obj in bw_objs: # Remove the objects from the world. try: self.world.remove(obj) except AttributeError: DeprecationWarning("Upgrade to Panda3d 1.9.") for attr in ("removeRigidBody", "removeConstraint", "removeGhost"): remove = getattr(self.world, attr) try: remove(obj) except TypeError: pass else: break def remove_all(self): """ Remove all objects from world.""" objs = (self.world.getCharacters() + self.world.getConstraints() + self.world.getGhosts() + self.world.getRigidBodies() + self.world.getSoftBodies() + self.world.getVehicles()) self.remove(objs) @property def gravity(self): """ Get gravity on self.world. """ return self.world.getGravity() @gravity.setter def gravity(self, gravity): """ Set gravity on self.world. """ self.world.setGravity(Vec3(*gravity)) def step(self, *args, **kwargs): """ Wrapper for BulletWorld.doPhysics.""" # Defaults. dt = args[0] if len(args) > 0 else self.sim_par["size"] n_subs = args[1] if len(args) > 1 else self.sim_par["n_subs"] size_sub = args[2] if len(args) > 2 else self.sim_par["size_sub"] force = kwargs.get("force", None) if force: bodies, vecpos, dur = force dt0 = np.clip(dur, 0., dt) n_subs0 = int(np.ceil(n_subs * dt0 / dt)) dt1 = dt - dt0 n_subs1 = n_subs - n_subs0 + 1 for body in bodies: body.applyForce(Vec3(*vecpos[0]), Point3(*vecpos[1])) # With force. self.world.doPhysics(dt0, n_subs0, size_sub) for body in bodies: body.clearForces() else: dt1 = dt n_subs1 = n_subs # With no force. self.world.doPhysics(dt1, n_subs1, size_sub) @staticmethod def attenuate_velocities(bodies, linvelfac=0., angvelfac=0.): """ Zeros out the bodies' linear and angular velocities.""" # Iterate through bodies, re-scaling their velocity vectors for body in bodies: linvel0 = body.getLinearVelocity() angvel0 = body.getAngularVelocity() # .normalize() returns True if the length is > 0 if linvel0.normalize(): linvel = linvel0 * linvelfac body.setLinearVelocity(linvel) if angvel0.normalize(): angvel = angvel0 * angvelfac body.setAngularVelocity(angvel) def repel(self, n_steps=1000, thresh=10, step_size=0.01): """ Performs n_steps physical "repel" steps. """ @contextmanager def repel_context(world): """ Sets up a repel context. Gets the bodies, turns off gravity, rescales the masses, sets up the collision notification callback. """ def change_contact_thresh(bodies, thresh=0.001): """ Adjust the contact processing threshold. This is used to make the objects not trigger collisions when just barely touching.""" if isinstance(thresh, Iterable): it = izip(bodies, thresh) else: it = ((body, thresh) for body in bodies) thresh0 = [] for body, th in it: thresh0.append(body.getContactProcessingThreshold()) body.setContactProcessingThreshold(th) return thresh0 def rescale_masses(bodies): """ Rescale the masses so they are proportional to the volume.""" masses, inertias = zip(*[(body.getMass(), body.getInertia()) for body in bodies]) volumefac = 1. for body, mass, inertia in zip(bodies, masses, inertias): # Compute the mass-normalized diagonal elements of the # inertia tensor. if mass > 0.: it = inertia / mass * 12 # Calculate volume from the mass-normalized # inertia tensor (from wikipedia). h = sqrt((it[0] - it[1] + it[2]) / 2) w = sqrt(it[2] - h ** 2) d = sqrt(it[1] - w ** 2) volume = h * w * d # Change the mass. body.setMass(volume * volumefac) return masses # Get the bodies. bodies = world.getRigidBodies() # Turn gravity off. gravity = world.getGravity() world.setGravity(Vec3.zero()) # Tighten the contact processing threshold slightly. delta = -0.001 cp_thresh = change_contact_thresh(bodies, thresh=delta) # Adjust masses. masses = rescale_masses(bodies) # Adjust sleep thresholds. deactivations = [b.isDeactivationEnabled() for b in bodies] for body in bodies: body.setDeactivationEnabled(False) # Zero out velocities. self.attenuate_velocities(bodies) # Collisions monitor. collisions = CollisionMonitor(world) collisions.push_notifiers(bodies) ## Finish __enter__. yield bodies, collisions ## Start __exit__. collisions.pop_notifiers() # Zero out velocities. self.attenuate_velocities(bodies) # Restore the contact processing threshold. change_contact_thresh(bodies, thresh=cp_thresh) # Set masses back. for body, mass in zip(bodies, masses): body.setMass(mass) # Turn gravity back on. world.setGravity(gravity) for body, d in zip(bodies, deactivations): body.setDeactivationEnabled(d) # Operate in a context that changes the masses, turns off # gravity, adds collision monitoring callback, etc. with repel_context(self.world) as (bodies, collisions): # Loop through the repel simulation. done_count = 0 for istep in xrange(n_steps): # Take one step. self.world.doPhysics(step_size, 1, step_size) # HACK: The following can be removed once Panda3d 1.9 # is installed (and the method can be removed from # CollisionMonitor). collisions.detect18() # Increment done_count, only if there are no contacts. if collisions: done_count = 0 else: done_count += 1 if any(body.getMass() > 0. and not body.isActive() for body in bodies): BP() # Stop criterion. if done_count >= thresh: break # Zero-out/re-scale velocities. linvelfac = bool(collisions) * 0.001 angvelfac = bool(collisions) * 0.001 self.attenuate_velocities(bodies, linvelfac, angvelfac) # Reset collisions. collisions.reset() return istep @classmethod def add_collide_mask(cls, func0): """ Decorator. Initializes ghost, static, and dynamic nodes with the appropriate collide masks.""" def func(*args, **kwargs): # Create node using func0. node = func0(*args, **kwargs) # Determine collide mask. if isinstance(node, BulletGhostNode): bit = cls.ghost_bit elif node.getMass() == 0.: bit = cls.static_bit else: bit = cls.dynamic_bit # Set collide mask. node.setCollideMask(bit) return node return update_wrapper(func0, func) @staticmethod def add_ghostnode(node): """ Adds a child ghostnode to a node as a workaround for the ghost-static node collision detection problem.""" name = "%s-ghost" % node.getName() ghost = NodePath(BulletGhostNode(name)) ghost.reparentTo(node) return ghost
class Panda3dBulletPhysics(World): # NOTE: the model ids of objects that correspond to opened doors. They will be ignored for collisions. openedDoorModelIds = [ '122', '133', '214', '246', '247', '361', '73', '756', '757', '758', '759', '760', '761', '762', '763', '764', '765', '768', '769', '770', '771', '778', '779', '780', 's__1762', 's__1763', 's__1764', 's__1765', 's__1766', 's__1767', 's__1768', 's__1769', 's__1770', 's__1771', 's__1772', 's__1773', ] # FIXME: is not a complete list of movable objects movableObjectCategories = [ 'table', 'dressing_table', 'sofa', 'trash_can', 'chair', 'ottoman', 'bed' ] # For more material, see table: http://www.ambrsoft.com/CalcPhysics/Density/Table_2.htm defaultDensity = 1000.0 # kg/m^3 # For more coefficients, see table: https://www.thoughtspike.com/friction-coefficients-for-bullet-physics/ defaultMaterialFriction = 0.7 defaultMaterialRestitution = 0.5 def __init__(self, scene, suncgDatasetRoot=None, debug=False, objectMode='box', agentRadius=0.1, agentHeight=1.6, agentMass=60.0, agentMode='capsule'): super(Panda3dBulletPhysics, self).__init__() self.__dict__.update(scene=scene, suncgDatasetRoot=suncgDatasetRoot, debug=debug, objectMode=objectMode, agentRadius=agentRadius, agentHeight=agentHeight, agentMass=agentMass, agentMode=agentMode) if suncgDatasetRoot is not None: self.modelCatMapping = ModelCategoryMapping( os.path.join(suncgDatasetRoot, "metadata", "ModelCategoryMapping.csv")) else: self.modelCatMapping = None self.bulletWorld = BulletWorld() self.bulletWorld.setGravity(Vec3(0, 0, -9.81)) if debug: debugNode = BulletDebugNode('physic-debug') debugNode.showWireframe(True) debugNode.showConstraints(False) debugNode.showBoundingBoxes(True) debugNode.showNormals(False) self.debugNodePath = self.scene.scene.attachNewNode(debugNode) self.debugNodePath.show() self.bulletWorld.setDebugNode(debugNode) else: self.debugNodePath = None self._initLayoutModels() self._initAgents() self._initObjects() self.scene.worlds['physics'] = self def destroy(self): # Nothing to do pass def _initLayoutModels(self): # Load layout objects as meshes for model in self.scene.scene.findAllMatches( '**/layouts/object*/model*'): shape, transform = getCollisionShapeFromModel( model, mode='mesh', defaultCentered=False) node = BulletRigidBodyNode('physics') node.setMass(0.0) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.setStatic(True) node.addShape(shape) node.setDeactivationEnabled(True) node.setIntoCollideMask(BitMask32.allOn()) self.bulletWorld.attach(node) # Attach the physic-related node to the scene graph physicsNp = model.getParent().attachNewNode(node) physicsNp.setTransform(transform) if node.isStatic(): model.getParent().setTag('physics-mode', 'static') else: model.getParent().setTag('physics-mode', 'dynamic') # Reparent render and acoustics nodes (if any) below the new physic node #XXX: should be less error prone to just reparent all children (except the hidden model) renderNp = model.getParent().find('**/render') if not renderNp.isEmpty(): renderNp.reparentTo(physicsNp) acousticsNp = model.getParent().find('**/acoustics') if not acousticsNp.isEmpty(): acousticsNp.reparentTo(physicsNp) # NOTE: we need this to update the transform state of the internal bullet node physicsNp.node().setTransformDirty() # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray(model.getNetTransform().getMat()), atol=1e-6) def _initAgents(self): # Load agents for agent in self.scene.scene.findAllMatches('**/agents/agent*'): transform = TransformState.makeIdentity() if self.agentMode == 'capsule': shape = BulletCapsuleShape( self.agentRadius, self.agentHeight - 2 * self.agentRadius) elif self.agentMode == 'sphere': shape = BulletCapsuleShape(self.agentRadius, 2 * self.agentRadius) # XXX: use BulletCharacterControllerNode class, which already handles local transform? node = BulletRigidBodyNode('physics') node.setMass(self.agentMass) node.setStatic(False) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.addShape(shape) self.bulletWorld.attach(node) # Constrain the agent to have fixed position on the Z-axis node.setLinearFactor(Vec3(1.0, 1.0, 0.0)) # Constrain the agent not to be affected by rotations node.setAngularFactor(Vec3(0.0, 0.0, 0.0)) node.setIntoCollideMask(BitMask32.allOn()) node.setDeactivationEnabled(True) # Enable continuous collision detection (CCD) node.setCcdMotionThreshold(1e-7) node.setCcdSweptSphereRadius(0.50) if node.isStatic(): agent.setTag('physics-mode', 'static') else: agent.setTag('physics-mode', 'dynamic') # Attach the physic-related node to the scene graph physicsNp = NodePath(node) physicsNp.setTransform(transform) # Reparent all child nodes below the new physic node for child in agent.getChildren(): child.reparentTo(physicsNp) physicsNp.reparentTo(agent) # NOTE: we need this to update the transform state of the internal bullet node physicsNp.node().setTransformDirty() # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray(agent.getNetTransform().getMat()), atol=1e-6) def _initObjects(self): # Load objects for model in self.scene.scene.findAllMatches( '**/objects/object*/model*'): modelId = model.getParent().getTag('model-id') # XXX: we could create BulletGhostNode instance for non-collidable objects, but we would need to filter out the collisions later on if not modelId in self.openedDoorModelIds: shape, transform = getCollisionShapeFromModel( model, self.objectMode, defaultCentered=True) node = BulletRigidBodyNode('physics') node.addShape(shape) node.setFriction(self.defaultMaterialFriction) node.setRestitution(self.defaultMaterialRestitution) node.setIntoCollideMask(BitMask32.allOn()) node.setDeactivationEnabled(True) if self.suncgDatasetRoot is not None: # Check if it is a movable object category = self.modelCatMapping.getCoarseGrainedCategoryForModelId( modelId) if category in self.movableObjectCategories: # Estimate mass of object based on volumetric data and default material density objVoxFilename = os.path.join(self.suncgDatasetRoot, 'object_vox', 'object_vox_data', modelId, modelId + '.binvox') voxelData = ObjectVoxelData.fromFile(objVoxFilename) mass = Panda3dBulletPhysics.defaultDensity * voxelData.getFilledVolume( ) node.setMass(mass) else: node.setMass(0.0) node.setStatic(True) else: node.setMass(0.0) node.setStatic(True) if node.isStatic(): model.getParent().setTag('physics-mode', 'static') else: model.getParent().setTag('physics-mode', 'dynamic') # Attach the physic-related node to the scene graph physicsNp = model.getParent().attachNewNode(node) physicsNp.setTransform(transform) # Reparent render and acoustics nodes (if any) below the new physic node #XXX: should be less error prone to just reparent all children (except the hidden model) renderNp = model.getParent().find('**/render') if not renderNp.isEmpty(): renderNp.reparentTo(physicsNp) acousticsNp = model.getParent().find('**/acoustics') if not acousticsNp.isEmpty(): acousticsNp.reparentTo(physicsNp) # NOTE: we need this to update the transform state of the internal bullet node node.setTransformDirty() # NOTE: we need to add the node to the bullet engine only after setting all attributes self.bulletWorld.attach(node) # Validation assert np.allclose( mat4ToNumpyArray(physicsNp.getNetTransform().getMat()), mat4ToNumpyArray( model.getParent().getNetTransform().getMat()), atol=1e-6) else: logger.debug('Object %s ignored from physics' % (modelId)) def step(self, dt): self.bulletWorld.doPhysics(dt) def isCollision(self, root): isCollisionDetected = False if isinstance(root.node(), BulletRigidBodyNode): result = self.bulletWorld.contactTest(root.node()) if result.getNumContacts() > 0: isCollisionDetected = True else: for nodePath in root.findAllMatches('**/+BulletBodyNode'): isCollisionDetected |= self.isCollision(nodePath) return isCollisionDetected def getCollisionInfo(self, root, dt): result = self.bulletWorld.contactTest(root.node()) force = 0.0 relativePosition = LVecBase3f(0.0, 0.0, 0.0) isCollisionDetected = False for _ in result.getContacts(): # Iterate over all manifolds of the world # NOTE: it seems like the contact manifold doesn't hold the information # to calculate contact force. We need the persistent manifolds for that. for manifold in self.bulletWorld.getManifolds(): # Check if the root node is part of that manifold, by checking positions # TODO: there is surely a better way to compare the two nodes here #if (manifold.getNode0().getTransform().getPos() == root.getNetTransform().getPos()): if manifold.getNode0().getTag('model-id') == root.getTag( 'model-id'): # Calculate the to totalImpulse = 0.0 maxImpulse = 0.0 for pt in manifold.getManifoldPoints(): impulse = pt.getAppliedImpulse() totalImpulse += impulse if impulse > maxImpulse: maxImpulse = impulse relativePosition = pt.getLocalPointA() force = totalImpulse / dt isCollisionDetected = True return force, relativePosition, isCollisionDetected def calculate2dNavigationMap(self, agent, z=0.1, precision=0.1, yup=True): agentRbNp = agent.find('**/+BulletRigidBodyNode') # Calculate the bounding box of the scene bounds = [] for nodePath in self.scene.scene.findAllMatches( '**/object*/+BulletRigidBodyNode'): node = nodePath.node() #NOTE: the bounding sphere doesn't seem to take into account the transform, so apply it manually (translation only) bsphere = node.getShapeBounds() center = nodePath.getNetTransform().getPos() bounds.extend( [center + bsphere.getMin(), center + bsphere.getMax()]) minBounds, maxBounds = np.min(bounds, axis=0), np.max(bounds, axis=0) # Using the X and Y dimensions of the bounding box, discretize the 2D plan into a uniform grid with given precision X = np.arange(minBounds[0], maxBounds[0], step=precision) Y = np.arange(minBounds[1], maxBounds[1], step=precision) nbTotalCells = len(X) * len(Y) threshold10Perc = int(nbTotalCells / 10) # XXX: the simulation needs to be run a little before moving the agent, not sure why self.bulletWorld.doPhysics(0.1) # Sweep the position of the agent across the grid, checking if collision/contacts occurs with objects or walls in the scene. occupancyMap = np.zeros((len(X), len(Y))) occupancyMapCoord = np.zeros((len(X), len(Y), 2)) n = 0 for i, x in enumerate(X): for j, y in enumerate(Y): agentRbNp.setPos(LVecBase3f(x, y, z)) if self.isCollision(agentRbNp): occupancyMap[i, j] = 1.0 occupancyMapCoord[i, j, 0] = x occupancyMapCoord[i, j, 1] = y n += 1 if n % threshold10Perc == 0: logger.debug('Collision test no.%d (out of %d total)' % (n, nbTotalCells)) if yup: # Convert to image format (y,x) occupancyMap = np.flipud(occupancyMap.T) occupancyMapCoord = np.flipud( np.transpose(occupancyMapCoord, axes=(1, 0, 2))) return occupancyMap, occupancyMapCoord
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(0, -10, 5) base.cam.look_at(0, 0, 0.2) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('up', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('down', 's') inputState.watchWithModifiers('right', 'd') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) if inputState.isSet('up'): force.y = 1.0 if inputState.isSet('down'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 force *= 300.0 self.bowlNP.node().set_active(True) self.bowlNP.node().apply_central_force(force) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.debugNP.node().show_wireframe(True) self.debugNP.node().show_constraints(True) self.debugNP.node().show_bounding_boxes(False) self.debugNP.node().show_normals(False) self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), 0) body = BulletRigidBodyNode('Ground') bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.set_pos(0, 0, 0) bodyNP.set_collide_mask(BitMask32.all_on()) self.world.attach(bodyNP.node()) # Bowl visNP = loader.load_model('models/bowl.egg') geom = (visNP.findAllMatches('**/+GeomNode').get_path( 0).node().get_geom(0)) mesh = BulletTriangleMesh() mesh.addGeom(geom) shape = BulletTriangleMeshShape(mesh, dynamic=True) body = BulletRigidBodyNode('Bowl') bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.node().set_mass(10.0) bodyNP.set_pos(0, 0, 0) bodyNP.set_collide_mask(BitMask32.all_on()) self.world.attach(bodyNP.node()) visNP.reparent_to(bodyNP) self.bowlNP = bodyNP self.bowlNP.set_scale(2) # Eggs self.eggNPs = [] for i in range(5): x = random.gauss(0, 0.1) y = random.gauss(0, 0.1) z = random.gauss(0, 0.1) + 1 h = random.random() * 360 p = random.random() * 360 r = random.random() * 360 visNP = loader.load_model('models/egg.egg') geom = (visNP.find_all_matches('**/+GeomNode').get_path( 0).node().get_geom(0)) shape = BulletConvexHullShape() shape.addGeom(geom) body = BulletRigidBodyNode('Egg-%i' % i) bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().set_mass(1.0) bodyNP.node().add_shape(shape) bodyNP.node().set_deactivation_enabled(False) bodyNP.set_collide_mask(BitMask32.all_on()) bodyNP.set_pos_hpr(x, y, z, h, p, r) #bodyNP.set_scale(1.5) self.world.attach(bodyNP.node()) visNP.reparent_to(bodyNP) self.eggNPs.append(bodyNP)
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(0, -80, 40) base.cam.look_at(0, 0, 10) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(0, 0, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def update(self, task): dt = globalClock.get_dt() self.world.do_physics(dt, 10, 0.008) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() @staticmethod def LVector3_rand(): x = 2 * random.random() - 1 y = 2 * random.random() - 1 z = 2 * random.random() - 1 return LVector3(x, y, z) def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground p0 = LPoint3(-20, -20, 0) p1 = LPoint3(-20, 20, 0) p2 = LPoint3(20, -20, 0) p3 = LPoint3(20, 20, 0) mesh = BulletTriangleMesh() mesh.add_triangle(p0, p1, p2) mesh.add_triangle(p1, p2, p3) shape = BulletTriangleMeshShape(mesh, dynamic=False) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Mesh')) np.node().add_shape(shape) np.set_pos(0, 0, -2) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Soft body world information info = self.world.get_world_info() info.set_air_density(1.2) info.set_water_density(0) info.set_water_offset(0) info.set_water_normal(LVector3(0, 0, 0)) # Softbody for i in range(50): p00 = LPoint3(-2, -2, 0) p10 = LPoint3(2, -2, 0) p01 = LPoint3(-2, 2, 0) p11 = LPoint3(2, 2, 0) node = BulletSoftBodyNode.make_patch(info, p00, p10, p01, p11, 6, 6, 0, True) node.generate_bending_constraints(2) node.get_cfg().set_lift_coefficient(0.004) node.get_cfg().set_dynamic_friction_coefficient(0.0003) node.get_cfg().set_aero_model( BulletSoftBodyConfig.AM_vertex_two_sided) node.set_total_mass(0.1) node.add_force(LVector3(0, 2, 0), 0) np = self.worldNP.attach_new_node(node) np.set_pos(self.LVector3_rand() * 10 + LVector3(0, 0, 20)) np.set_hpr(self.LVector3_rand() * 16) self.world.attach(node) fmt = GeomVertexFormat.get_v3n3t2() geom = BulletHelper.make_geom_from_faces(node, fmt, True) node.link_geom(geom) nodeV = GeomNode('') nodeV.add_geom(geom) npV = np.attach_new_node(nodeV) tex = loader.load_texture('models/panda.jpg') npV.set_texture(tex) BulletHelper.make_texcoords_for_patch(geom, 6, 6)
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(0, -20, 5) base.cam.look_at(0, 0, 5) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) self.accept('enter', self.do_shoot) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') def do_shoot(self): # Get from/to points from mouse click pMouse = base.mouseWatcherNode.get_mouse() pFrom = LPoint3() pTo = LPoint3() base.camLens.extrude(pMouse, pFrom, pTo) pFrom = render.get_relative_point(base.cam, pFrom) pTo = render.get_relative_point(base.cam, pTo) # Calculate initial velocity v = pTo - pFrom v.normalize() v *= 100.0 # Create bullet shape = BulletSphereShape(0.3) body = BulletRigidBodyNode('Bullet') bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.node().set_mass(1.0) bodyNP.node().set_linear_velocity(v) bodyNP.node().set_ccd_motion_threshold(1e-7) bodyNP.node().set_ccd_swept_sphere_radius(0.50) bodyNP.set_collide_mask(BitMask32.all_on()) bodyNP.set_pos(pFrom) visNP = loader.load_model('models/ball.egg') visNP.set_scale(0.8) visNP.reparent_to(bodyNP) self.world.attach(bodyNP.node()) # Remove the bullet again after 2 seconds taskMgr.do_method_later(2, self.do_remove, 'doRemove', extraArgs=[bodyNP], appendTask=True) def do_remove(self, bodyNP, task): self.world.remove(bodyNP.node()) bodyNP.remove_node() return task.done # ____TASK___ def update(self, task): dt = globalClock.get_dt() self.world.do_physics(dt, 20, 1.0 / 180.0) return task.cont def cleanup(self): self.worldNP.remove_node() self.worldNP = None self.world = None def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.debugNP.node().show_wireframe(True) self.debugNP.node().show_constraints(True) self.debugNP.node().show_bounding_boxes(False) self.debugNP.node().show_normals(False) self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Box A shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) bodyA = BulletRigidBodyNode('Box A') bodyNP = self.worldNP.attach_new_node(bodyA) bodyNP.node().add_shape(shape) bodyNP.set_collide_mask(BitMask32.all_on()) bodyNP.set_pos(-4, 0, 4) visNP = loader.load_model('models/box.egg') visNP.clear_model_nodes() visNP.reparent_to(bodyNP) self.world.attach(bodyA) # Box B shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) bodyB = BulletRigidBodyNode('Box B') bodyNP = self.worldNP.attach_new_node(bodyB) bodyNP.node().add_shape(shape) bodyNP.node().set_mass(1.0) bodyNP.node().set_deactivation_enabled(False) bodyNP.set_collide_mask(BitMask32.all_on()) bodyNP.set_pos(1, 0, 0) visNP = loader.load_model('models/box.egg') visNP.clear_model_nodes() visNP.reparent_to(bodyNP) self.world.attach(bodyB) # Generic frameA = TransformState.make_pos_hpr(LPoint3(4, 0, 0), LVector3(0, 0, 0)) frameB = TransformState.make_pos_hpr(LPoint3(2, 0, 0), LVector3(0, 0, 0)) generic = BulletGenericConstraint(bodyA, bodyB, frameA, frameB, True) generic.set_debug_draw_size(2.0) generic.set_linear_limit(0, 0, 0) generic.set_linear_limit(1, 0, 3) generic.set_linear_limit(2, 0, 1) generic.set_angular_limit(0, -60, 60) generic.set_angular_limit(1, -30, 30) generic.set_angular_limit(2, -120, 120) self.world.attach(generic)
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(0, -60, 20) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def update(self, task): dt = globalClock.get_dt() self.world.do_physics(dt, 10, 0.008) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() #self.debugNP.show_tight_bounds() #self.debugNP.show_bounds() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground p0 = LPoint3(-20, -20, 0) p1 = LPoint3(-20, 20, 0) p2 = LPoint3(20, -20, 0) p3 = LPoint3(20, 20, 0) mesh = BulletTriangleMesh() mesh.add_triangle(p0, p1, p2) mesh.add_triangle(p1, p2, p3) shape = BulletTriangleMeshShape(mesh, dynamic=False) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Mesh')) np.node().add_shape(shape) np.set_pos(0, 0, -2) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Stair origin = LPoint3(0, 0, 0) size = LVector3(2, 10, 1) shape = BulletBoxShape(size * 0.5) for i in range(10): pos = origin + size * i pos.setY(0) np = self.worldNP.attach_new_node( BulletRigidBodyNode('Stair{}'.format(i))) np.node().add_shape(shape) np.set_pos(pos) np.set_collide_mask(BitMask32.all_on()) npV = loader.load_model('models/box.egg') npV.reparent_to(np) npV.set_scale(size) self.world.attach(np.node()) # Soft body world information info = self.world.get_world_info() info.set_air_density(1.2) info.set_water_density(0) info.set_water_offset(0) info.set_water_normal(LVector3(0, 0, 0)) # Softbody center = LPoint3(0, 0, 0) radius = LVector3(1, 1, 1) * 1.5 node = BulletSoftBodyNode.make_ellipsoid(info, center, radius, 128) node.set_name('Ellipsoid') node.get_material(0).set_linear_stiffness(0.1) node.get_cfg().set_dynamic_friction_coefficient(1) node.get_cfg().set_damping_coefficient(0.001) node.get_cfg().set_pressure_coefficient(1500) node.set_total_mass(30, True) node.set_pose(True, False) np = self.worldNP.attach_new_node(node) np.set_pos(15, 0, 12) #np.setH(90.0) #np.show_bounds() #np.show_tight_bounds() self.world.attach(np.node()) geom = BulletHelper.make_geom_from_faces(node) node.link_geom(geom) nodeV = GeomNode('EllipsoidVisual') nodeV.add_geom(geom) npV = np.attach_new_node(nodeV)
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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 self.boxNP.node().set_active(True) self.boxNP.node().apply_central_force(force) self.boxNP.node().apply_torque(torque) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) self.world.set_filter_callback(PythonCallbackObject(self.filter)) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), -1) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_python_tag('foo', 2) self.world.attach(np.node()) # Box 1 shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-1')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(3, 0, 4) np.set_python_tag('foo', 0) self.world.attach(np.node()) self.boxNP = np # Box 2 shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box-2')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(-3, 0, 4) np.set_python_tag('foo', -1) self.world.attach(np.node()) def filter(self, cb_data): """ cb_data is of type BulletFilterCallbackData. A rather silly collision filtering algorithm. We assume every node has the Python tag 'foo' set, and that the value of this tag is integer. Then we add the values and if the result is greater than zero we want the nodes to collide. """ x1 = cb_data.get_node_0().get_python_tag('foo') x2 = cb_data.get_node_1().get_python_tag('foo') cb_data.set_collide(x1 + x2 > 0)
# node.addShape(shape) # np = base.render.attachNewNode(node) # np.setPos(0, 0, 0) model = cm.CollisionModel("./objects/bunnysim.meshes") # model.reparentTo(base.render) # model.setMat(Mat4.rotateMat(10, Vec3(1,0,0))) # model.setPos(0,0,300) bulletnode = bch.genBulletCDMesh(model) bulletnode.setMass(1) rigidbody = base.render.attachNewNode(bulletnode) model.reparentTo(rigidbody) world = BulletWorld() world.setGravity(Vec3(0, 0, -9.8)) world.attach(bulletnode) def update(task): dt = globalClock.getDt() world.doPhysics(dt) # vecw= topbullnode.getAngularVelocity() # arrownp = pg.plotArrow(base.render, epos = vecw*1000, thickness = 15) # print rotmat # model.setMat(base.pg.np4ToMat4(rm.homobuild(rbd.pos, rbd.rotmat))) return task.cont taskMgr.add(update, 'update') base.run()
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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): if not self.box: return force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 self.box.set_active(True) self.box.apply_central_force(force) self.box.apply_torque(torque) def process_contacts(self): if not self.box or not self.sphere: return result = self.world.contact_test_pair(self.box, self.sphere) #result = self.world.contact_test(self.box) #print '-->', result.get_num_contacts() for contact in result.get_contacts(): cp = contact.get_manifold_point() node0 = contact.get_node0() node1 = contact.get_node1() print(node0.get_name(), node1.get_name(), cp.get_local_point_a()) #print(contact.get_node0(), cp.get_position_world_on_a()) #print(contact.get_idx0(), contact.get_idx1(), \ # contact.get_part_id0(), contact.get_part_id1()) self.remove_node(node1) def remove_node(self, node): self.world.remove(node) if node == self.sphere: self.sphere = None if node == self.box: self.box = None np = NodePath(node) np.remove_node() def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt, 10, 0.008) self.process_contacts() return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() #def on_contact_added(self, node1, node2): # print('contact added:', node1, node2) #def on_contact_destroyed(self, node1, node2): # print('contact destroyed:', node1, node2) def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Plane shape = BulletPlaneShape(LVector3(0, 0, 1), 0) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Box shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box')) np.node().set_mass(1.0) np.node().add_shape(shape) np.node().set_deactivation_enabled(False) np.set_pos(2, 0, 4) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) visualNP = loader.load_model('models/box.egg') visualNP.reparent_to(np) self.box = np.node() # Sphere shape = BulletSphereShape(0.6) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Sphere')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(-2, 0, 4) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) self.sphere = np.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(0, -40, 10) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(5, 0, -2)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def update(self, task): dt = globalClock.get_dt() self.world.do_physics(dt, 10, 0.008) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground p0 = LPoint3(-20, -20, 0) p1 = LPoint3(-20, 20, 0) p2 = LPoint3(20, -20, 0) p3 = LPoint3(20, 20, 0) mesh = BulletTriangleMesh() mesh.add_triangle(p0, p1, p2) mesh.add_triangle(p1, p2, p3) shape = BulletTriangleMeshShape(mesh, dynamic=False) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Mesh')) np.node().add_shape(shape) np.set_pos(0, 0, -4) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Soft body world information info = self.world.get_world_info() info.set_air_density(1.2) info.set_water_density(0) info.set_water_offset(0) info.set_water_normal(LVector3(0, 0, 0)) ## Softbody - From points/indices #import cube #points = [LPoint3(x,y,z) * 3 for x,y,z in cube.nodes] #indices = sum([list(x) for x in cube.elements], []) #node = BulletSoftBodyNode.make_tet_mesh(info, points, indices, True) #node.set_volume_mass(300); #node.get_shape(0).set_margin(0.01) #node.get_material(0).set_linear_stiffness(0.8) #node.get_cfg().set_positions_solver_iterations(1) #node.get_cfg().clear_all_collision_flags() #node.get_cfg().set_collision_flag( #BulletSoftBodyConfig.CF_cluster_soft_soft, True) #node.get_cfg().set_collision_flag( #BulletSoftBodyConfig.CF_cluster_rigid_soft, True) #node.generate_clusters(16) #softNP = self.worldNP.attach_new_node(node) #softNP.set_pos(0, 0, 8) #softNP.set_hpr(0, 0, 45) #self.world.attach(node) # Softbody - From tetgen data ele = open('models/cube/cube.1.ele', 'r').read() face = open('models/cube/cube.1.face', 'r').read() node = open('models/cube/cube.1.node', 'r').read() node = BulletSoftBodyNode.make_tet_mesh(info, ele, face, node) node.set_name('Tetra') node.set_volume_mass(300) node.get_shape(0).set_margin(0.01) node.get_material(0).set_linear_stiffness(0.1) node.get_cfg().set_positions_solver_iterations(1) node.get_cfg().clear_all_collision_flags() node.get_cfg().set_collision_flag( BulletSoftBodyConfig.CF_cluster_soft_soft, True) node.get_cfg().setCollisionFlag( BulletSoftBodyConfig.CF_cluster_rigid_soft, True) node.generate_clusters(6) softNP = self.worldNP.attach_new_node(node) softNP.set_pos(0, 0, 8) softNP.set_hpr(45, 0, 0) self.world.attach(node) # Option 1: visNP = loader.load_model('models/cube/cube.egg') visNP.reparent_to(softNP) geom = (visNP.findAllMatches('**/+GeomNode').getPath( 0).node().modifyGeom(0)) node.link_geom(geom)
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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 self.boxNP.node().set_active(True) self.boxNP.node().apply_central_force(force) self.boxNP.node().apply_torque(torque) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt) self.raycast() return task.cont def raycast(self): pFrom = LPoint3(-4, 0, 0.5) pTo = LPoint3(4, 0, 0.5) #pTo = pFrom + LVector3(1, 0, 0) * 99999 # Raycast for closest hit result = self.world.ray_test_closest(pFrom, pTo) print(result.has_hit(), \ result.get_hit_fraction(), \ result.get_node(), \ result.get_hit_pos(), \ result.get_hit_normal()) # Raycast for all hits #result = self.world.ray_test_all(pFrom, pTo) #print result.has_hits(), \ # result.get_closest_hit_fraction(), \ # result.get_num_hits() #print [hit.get_hit_pos() for hit in result.get_hits()] def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), 0) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, 0) np.set_collide_mask(BitMask32(0x0f)) self.world.attach(np.node()) # Box shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Box')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(0, 0, 4) np.set_collide_mask(BitMask32(0x0f)) self.world.attach(np.node()) self.boxNP = np visualNP = loader.load_model('models/box.egg') visualNP.reparent_to(self.boxNP) # Sphere shape = BulletSphereShape(0.6) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Sphere')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(3, 0, 4) np.set_collide_mask(BitMask32(0x0f)) self.world.attach(np.node()) # Cone shape = BulletConeShape(0.6, 1.0) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Cone')) np.node().set_mass(1.0) np.node().add_shape(shape) np.set_pos(6, 0, 4) np.set_collide_mask(BitMask32(0x0f)) self.world.attach(np.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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('turnLeft', 'a') inputState.watchWithModifiers('turnRight', 'd') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): engineForce = 0.0 brakeForce = 0.0 if inputState.isSet('forward'): engineForce = 1000.0 brakeForce = 0.0 if inputState.isSet('reverse'): engineForce = 0.0 brakeForce = 100.0 if inputState.isSet('turnLeft'): self.steering += dt * self.steeringIncrement self.steering = min(self.steering, self.steeringClamp) if inputState.isSet('turnRight'): self.steering -= dt * self.steeringIncrement self.steering = max(self.steering, -self.steeringClamp) # Apply steering to front wheels self.vehicle.setSteeringValue(self.steering, 0); self.vehicle.setSteeringValue(self.steering, 1); # Apply engine and brake to rear wheels self.vehicle.applyEngineForce(engineForce, 2); self.vehicle.applyEngineForce(engineForce, 3); self.vehicle.setBrake(brakeForce, 2); self.vehicle.setBrake(brakeForce, 3); def update(self, task): dt = globalClock.get_dt() self.process_input(dt) self.world.do_physics(dt, 10, 0.008) #print self.vehicle.getWheel(0).getRaycastInfo().isInContact() #print self.vehicle.getWheel(0).getRaycastInfo().getContactPointWs() #print self.vehicle.getChassis().isKinematic() return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Plane shape = BulletPlaneShape(LVector3(0, 0, 1), 0) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Ground')) np.node().add_shape(shape) np.set_pos(0, 0, -1) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Chassis shape = BulletBoxShape(LVector3(0.6, 1.4, 0.5)) ts = TransformState.make_pos(LPoint3(0, 0, 0.5)) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Vehicle')) np.node().add_shape(shape, ts) np.set_pos(0, 0, 1) np.node().set_mass(800.0) np.node().set_deactivation_enabled(False) self.world.attach(np.node()) #np.node().set_ccd_swept_sphere_radius(1.0) #np.node().set_ccd_motion_threshold(1e-7) # Vehicle self.vehicle = BulletVehicle(self.world, np.node()) self.vehicle.set_coordinate_system(ZUp) self.world.attach(self.vehicle) self.yugoNP = loader.load_model('models/yugo/yugo.egg') self.yugoNP.reparent_to(np) # Right front wheel np = loader.load_model('models/yugo/yugotireR.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3( 0.70, 1.05, 0.3), True, np) # Left front wheel np = loader.load_model('models/yugo/yugotireL.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3(-0.70, 1.05, 0.3), True, np) # Right rear wheel np = loader.load_model('models/yugo/yugotireR.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3( 0.70, -1.05, 0.3), False, np) # Left rear wheel np = loader.load_model('models/yugo/yugotireL.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3(-0.70, -1.05, 0.3), False, np) # Steering info self.steering = 0.0 # degree self.steeringClamp = 45.0 # degree self.steeringIncrement = 120.0 # degree per second def add_wheel(self, pos, front, np): wheel = self.vehicle.create_wheel() wheel.set_node(np.node()) wheel.set_chassis_connection_point_cs(pos) wheel.set_front_wheel(front) wheel.set_wheel_direction_cs(LVector3(0, 0, -1)) wheel.set_wheel_axle_cs(LVector3(1, 0, 0)) wheel.set_wheel_radius(0.25) wheel.set_max_suspension_travel_cm(40.0) wheel.set_suspension_stiffness(40.0) wheel.set_wheels_damping_relaxation(2.3) wheel.set_wheels_damping_compression(4.4) wheel.set_friction_slip(100.0); wheel.set_roll_influence(0.1)
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 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(0, -20, 4) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('left', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('right', 'd') inputState.watchWithModifiers('turnLeft', 'q') inputState.watchWithModifiers('turnRight', 'e') # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def process_input(self, dt): force = LVector3(0, 0, 0) torque = LVector3(0, 0, 0) if inputState.isSet('forward'): force.y = 1.0 if inputState.isSet('reverse'): force.y = -1.0 if inputState.isSet('left'): force.x = -1.0 if inputState.isSet('right'): force.x = 1.0 if inputState.isSet('turnLeft'): torque.z = 1.0 if inputState.isSet('turnRight'): torque.z = -1.0 force *= 30.0 torque *= 10.0 force = render.get_relative_vector(self.boxNP, force) torque = render.get_relative_vector(self.boxNP, torque) self.boxNP.node().set_active(True) self.boxNP.node().apply_central_force(force) self.boxNP.node().apply_torque(torque) def update(self, task): dt = globalClock.get_dt() self.process_input(dt) #self.world.doPhysics(dt) self.world.do_physics(dt, 5, 1.0 / 180.0) return task.cont def cleanup(self): self.world.remove(self.groundNP.node()) self.world.remove(self.boxNP.node()) self.world = None self.debugNP = None self.groundNP = None self.boxNP = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.debugNP.node().show_wireframe(True) self.debugNP.node().show_constraints(True) self.debugNP.node().show_bounding_boxes(False) self.debugNP.node().show_normals(True) #self.debugNP.show_tight_bounds() #self.debugNP.show_bounds() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground (static) shape = BulletPlaneShape(LVector3(0, 0, 1), 1) self.groundNP = self.worldNP.attach_new_node( BulletRigidBodyNode('Ground')) self.groundNP.node().add_shape(shape) self.groundNP.set_pos(0, 0, -2) self.groundNP.set_collide_mask(BitMask32.all_on()) self.world.attach(self.groundNP.node()) # Box (dynamic) shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) self.boxNP = self.worldNP.attach_new_node(BulletRigidBodyNode('Box')) self.boxNP.node().set_mass(1.0) self.boxNP.node().add_shape(shape) self.boxNP.set_pos(0, 0, 2) #self.boxNP.set_scale(2, 1, 0.5) self.boxNP.set_collide_mask(BitMask32.all_on()) #self.boxNP.node().set_deactivation_enabled(False) self.world.attach(self.boxNP.node()) visualNP = loader.load_model('models/box.egg') visualNP.clear_model_nodes() visualNP.reparent_to(self.boxNP)
class HeightfieldVehicle(ShowBase): def __init__(self, heightfield_fn="heightfield.png"): # Store the heightfield's filename. self.heightfield_fn = heightfield_fn """ Load some configuration variables, it's important for this to happen before ShowBase is initialized """ load_prc_file_data("", """ sync-video #t textures-power-2 none ###gl-coordinate-system default notify-level-gobj warning notify-level-grutil debug notify-level-shader_terrain debug notify-level-bullet debug ### model paths model-path /usr/share/panda3d model-path /home/juzzuj/code/prereq/bullet-samples/bullet-samples """) ShowBase.__init__(self) base.set_background_color(0.1, 0.1, 0.8, 1) base.set_frame_rate_meter(True) # Increase camera Field Of View and set near and far planes base.camLens.set_fov(90) base.camLens.set_near_far(0.1, 50000) # Lights alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Basic game controls self.accept('escape', self.do_exit) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) self.accept('r', self.do_reset) # Vehicle controls inputState.watchWithModifiers('forward', 'w') inputState.watchWithModifiers('turnLeft', 'a') inputState.watchWithModifiers('reverse', 's') inputState.watchWithModifiers('turnRight', 'd') inputState.watchWithModifiers('forward', 'arrow_up') inputState.watchWithModifiers('turnLeft', 'arrow_left') inputState.watchWithModifiers('reverse', 'arrow_down') inputState.watchWithModifiers('turnRight', 'arrow_right') self.accept('space', self.reset_vehicle) # Controls to do with the terrain #self.accept('t', self.rise_in_front) self.accept('t', self.deform_terrain, ["raise"]) self.accept('g', self.deform_terrain, ["depress"]) self.accept('b', self.drop_boxes) # Some debugging and miscellaneous controls self.accept('e', self.query_elevation) self.accept('c', self.convert_coordinates) self.accept('p', self.save) self.accept('h', self.hide_terrain) # Task taskMgr.add(self.update, 'updateWorld') self.setup() """ Macro-like function used to reduce the amount to code needed to create the on screen instructions """ def genLabelText(self, i, text, parent="a2dTopLeft"): return OnscreenText(text=text, parent=getattr(base, parent), scale=.05, pos=(0.06, -.065 * i), fg=(1, 1, 1, 1), align=TextNode.ALeft) # _____HANDLER_____ def cleanup(self): self.world = None self.worldNP.remove_node() self.terrain.remove_node() self.skybox.remove_node() del self.terrain_node def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('HeightfieldVehicle') # ____TASK___ def update(self, task): # Get the time passed since the last frame dt = globalClock.get_dt() # Pass dt as parameter (we need it for sensible steering calculations) self.process_input(dt) """ Basically, we want to put our camera where the camera floater is. We need the floater's world (=render-relative) position (it's parented to the vehicle) """ floater_pos = render.get_relative_point(self.camera_floater, LVector3(0)) """ If the camera floater is under the terrain surface, adjust it, so that it stays above the terrain. """ elevation_at_floater_pos = self.query_elevation(floater_pos) if elevation_at_floater_pos.z >= floater_pos.z: floater_pos.z = elevation_at_floater_pos.z + 1. # Now we set our camera's position and make it look at the vehicle. base.cam.set_pos(floater_pos) base.cam.look_at(self.vehicleNP) # Let the Bullet library do physics calculations. self.world.do_physics(dt, 10, 0.008) return task.cont def process_input(self, dt): # Relax steering towards straight self.steering *= 1 - self.steering_relax_factor * dt engine_force = 0.0 brake_force = 0.0 if inputState.isSet('forward'): engine_force = 1000.0 brake_force = 0.0 if inputState.isSet('reverse'): engine_force = 0.0 brake_force = 100.0 if inputState.isSet('turnLeft'): self.steering += dt * self.steering_increment self.steering = min(self.steering, self.steering_clamp) if inputState.isSet('turnRight'): self.steering -= dt * self.steering_increment self.steering = max(self.steering, -self.steering_clamp) # Lower steering intensity for high speeds self.steering *= 1. - (self.vehicle.get_current_speed_km_hour() * self.steering_speed_reduction_factor) # Apply steering to front wheels #self.vehicle.get_wheels()[0].set_steering(self.steering) #self.vehicle.get_wheels()[1].set_steering(self.steering) #self.vehicle.wheels[0].steering = self.steering #self.vehicle.wheels[1].steering = self.steering self.vehicle.set_steering_value(self.steering, 0) self.vehicle.set_steering_value(self.steering, 1) # Apply engine and brake to rear wheels #self.vehicle.wheels[2].engine_force = engine_force #self.vehicle.wheels[3].engine_force = engine_force #self.vehicle.wheels[2].brake = brake_force #self.vehicle.wheels[3].brake = brake_force self.vehicle.apply_engine_force(engine_force, 2) self.vehicle.apply_engine_force(engine_force, 3) self.vehicle.set_brake(brake_force, 2) self.vehicle.set_brake(brake_force, 3) def setup(self): # Bullet physics world self.worldNP = render.attach_new_node('World') self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Vehicle # Steering info self.steering = 0.0 # degrees self.steering_clamp = 45.0 # degrees self.steering_increment = 120.0 # degrees per second # How fast steering relaxes to straight ahead self.steering_relax_factor = 2.0 # How much steering intensity decreases with higher speeds self.steering_speed_reduction_factor = 0.003 # Chassis collision box (note: Bullet uses half-measures) shape = BulletBoxShape(LVector3(0.6, 1.4, 0.5)) ts = TransformState.make_pos(LPoint3(0, 0, 0.5)) self.vehicleNP = self.worldNP.attach_new_node( BulletRigidBodyNode('Vehicle')) self.vehicleNP.node().add_shape(shape, ts) # Set initial position self.vehicleNP.set_pos(0, 70, -10) self.vehicleNP.node().set_mass(800.0) self.vehicleNP.node().set_deactivation_enabled(False) self.world.attach(self.vehicleNP.node()) # Camera floater self.attach_camera_floater() # BulletVehicle self.vehicle = BulletVehicle(self.world, self.vehicleNP.node()) self.world.attach(self.vehicle) # Vehicle model self.yugoNP = loader.load_model('models/yugo/yugo.egg') self.yugoNP.reparent_to(self.vehicleNP) # Right front wheel np = loader.load_model('models/yugo/yugotireR.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3( 0.70, 1.05, 0.3), True, np) # Left front wheel np = loader.load_model('models/yugo/yugotireL.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3(-0.70, 1.05, 0.3), True, np) # Right rear wheel np = loader.load_model('models/yugo/yugotireR.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3( 0.70, -1.05, 0.3), False, np) # Left rear wheel np = loader.load_model('models/yugo/yugotireL.egg') np.reparent_to(self.worldNP) self.add_wheel(LPoint3(-0.70, -1.05, 0.3), False, np) # Load a skybox self.skybox = self.loader.load_model( "samples/shader-terrain/models/skybox.bam") self.skybox.reparent_to(self.render) self.skybox.set_scale(20000) skybox_texture = self.loader.load_texture( "samples/shader-terrain/textures/skybox.jpg") skybox_texture.set_minfilter(SamplerState.FT_linear) skybox_texture.set_magfilter(SamplerState.FT_linear) skybox_texture.set_wrap_u(SamplerState.WM_repeat) skybox_texture.set_wrap_v(SamplerState.WM_mirror) skybox_texture.set_anisotropic_degree(16) self.skybox.set_texture(skybox_texture) skybox_shader = Shader.load(Shader.SL_GLSL, "samples/shader-terrain/skybox.vert.glsl", "samples/shader-terrain/skybox.frag.glsl") self.skybox.set_shader(skybox_shader) # Terrain self.setup_terrain() def add_wheel(self, pos, front, np): wheel = self.vehicle.create_wheel() wheel.set_node(np.node()) wheel.set_chassis_connection_point_cs(pos) wheel.set_front_wheel(front) wheel.set_wheel_direction_cs(LVector3(0, 0, -1)) wheel.set_wheel_axle_cs(LVector3(1, 0, 0)) wheel.set_wheel_radius(0.25) wheel.set_max_suspension_travel_cm(40.0) wheel.set_suspension_stiffness(40.0) wheel.set_wheels_damping_relaxation(2.3) wheel.set_wheels_damping_compression(4.4) wheel.set_friction_slip(100.0) wheel.set_roll_influence(0.1) def attach_camera_floater(self): """ Set up an auxiliary camera floater, which is parented to the vehicle. Every frame base.cam's position will be set to the camera floater's. """ camera_behind = 8 camera_above = 3 self.camera_floater = NodePath("camera_floater") self.camera_floater.reparent_to(self.vehicleNP) self.camera_floater.set_y(-camera_behind) self.camera_floater.set_z(camera_above) def reset_vehicle(self): reset_pos = self.vehicleNP.get_pos() reset_pos.z += 3 self.vehicleNP.node().clear_forces() self.vehicleNP.node().set_linear_velocity(LVector3(0)) self.vehicleNP.node().set_angular_velocity(LVector3(0)) self.vehicleNP.set_pos(reset_pos) self.vehicleNP.set_hpr(LVector3(0)) 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 query_elevation(self, xy_pos=None): """ Query elevation for xy_pos if present, else for vehicle position. No xy_pos means verbose mode. """ query_pos = xy_pos or self.vehicleNP.get_pos() """ This method is accurate and may be useful for placing objects on the terrain surface. """ result = self.world.ray_test_closest( LPoint3(query_pos.x, query_pos.y, -10000), LPoint3(query_pos.x, query_pos.y, 10000)) if result.has_hit(): hit_pos = result.get_hit_pos() if not xy_pos: print("Bullet heightfield elevation at " "X {:.2f} | Y {:.2f} is {:.3f}".format( hit_pos.x, hit_pos.y, hit_pos.z)) else: hit_pos = None if not xy_pos: print("Could not query elevation at {}".format(xy_pos)) """ This method is less accurate than the one above. Under heavy ray-testing stress (ray tests are performed for all vehicle wheels, the above elevation query etc.) Bullet sometimes seems to be a little unreliable. """ texspace_pos = self.terrain.get_relative_point(render, query_pos) stm_pos = self.terrain_node.uv_to_world( LTexCoord(texspace_pos.x, texspace_pos.y)) if not xy_pos: print("ShaderTerrainMesh elevation at " "X {:.2f} | Y {:.2f} is {:.3f}".format( stm_pos.x, stm_pos.y, stm_pos.z)) return hit_pos or stm_pos 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 deform_terrain(self, mode="raise", duration=1.0): self.deform_duration = duration """ Calculate distance to the spot at which we want to raise the terrain. At its current speed the vehicle would reach it in 2.5 seconds. [km/h] / 3.6 = [m/s] * [s] = [m] """ distance = self.vehicle.get_current_speed_km_hour() / 3.6 * 2.5 spot_pos_world = (self.vehicleNP.get_pos() + self.vehicle.get_forward_vector() * distance) spot_pos_heightfield = self.terrain_node.world_to_heightfield( spot_pos_world) xcenter = spot_pos_heightfield.x ycenter = spot_pos_heightfield.y """ To create a smooth hill/depression we call PfmFile.pull_spot to create a sort of gradient. You can use self.cardmaker_debug to view it. From the Panda3D API documentation of PfmFile.pull_spot: Applies delta * t to the point values within radius (xr, yr) distance of (xc, yc). The t value is scaled from 1.0 at the center to 0.0 at radius (xr, yr), and this scale follows the specified exponent. Returns the number of points affected. """ # Delta to apply to the point values in DynamicHeightfield array. delta = LVector4(0.001) # Make the raised spot elliptical. xradius = 10.0 # meters yradius = 6.0 # meters # Choose an exponent exponent = 0.6 # Counter-clockwise angle between Y-axis angle = self.vehicle.get_forward_vector().signed_angle_deg( LVector3.forward(), LVector3.down()) # Define all we need to repeatedly deform the terrain using a task. self.spot_params = [delta, xcenter, ycenter, xradius, yradius, exponent, mode] # Clear staging area to a size that fits our raised region. self.StagingPFM.clear(int(xradius)*2, int(yradius)*2, num_channels=1) """ There are two options: (1) Rotate our hill/depression to be perpendicular to the vehicle's trajectory. This is the default. (2) Just put it in the terrain unrotated. """ # Option (1) self.StagingPFM.pull_spot(delta, xradius-0.5, yradius-0.5, xradius, yradius, exponent) # Rotate wider side so it's perpendicular to vehicle's trajectory. self.RotorPFM.rotate_from(self.StagingPFM, angle) self.raise_start_time = globalClock.get_real_time() taskMgr.do_method_later(0.03, self.deform_perpendicular, "DeformPerpendicularSpot") # Option (2) #taskMgr.do_method_later(0.03, self.deform_regular, "RaiseASpot") def deform_perpendicular(self, task): if (globalClock.get_real_time() - self.raise_start_time < self.deform_duration): (delta, xcenter, ycenter, xradius, yradius, exponent, mode) = self.spot_params """ Copy rotated hill to our DynamicHeightfield. The values from RotorPFM are added to the ones found in the DynamicHeightfield. """ self.DHF.add_sub_image(self.RotorPFM, int(xcenter - self.RotorPFM.get_x_size()/2), int(ycenter - self.RotorPFM.get_y_size()/2), 0, 0, self.RotorPFM.get_x_size(), self.RotorPFM.get_y_size(), 1.0 if mode == "raise" else -1.0) # Output images of our PfmFiles. #self.RotorPFM.write("dbg_RotorPFM.png") #self.StagingPFM.write("dbg_StagingPFM.png") return task.again self.cardmaker_debug() return task.done def deform_regular(self, task): if (globalClock.get_real_time() - self.raise_start_time < self.deform_duration): (delta, xcenter, ycenter, xradius, yradius, exponent, mode) = self.spot_params self.DHF.pull_spot(delta * (1.0 if mode == "raise" else -1.0), xcenter, ycenter, xradius, yradius, exponent) return task.again return task.done def convert_coordinates(self): vpos = self.vehicleNP.get_pos() print("VPOS world: ", vpos) W2H = self.terrain_node.world_to_heightfield(vpos) print("W2H: ", W2H) H2W = self.terrain_node.heightfield_to_world(LTexCoord(W2H.x, W2H.y)) print("H2W: ", H2W) W2U = self.terrain_node.world_to_uv(vpos) print("W2U: ", W2U) U2W = self.terrain_node.uv_to_world(LTexCoord(W2U.x, W2U.y)) print("U2W: ", U2W) def hide_terrain(self): if self.terrain.is_hidden(): self.terrain.show() else: self.terrain.hide() def save(self): self.DHF.write("dbg_dynamicheightfield.png") def pfmvizzer_debug(self, pfm): vizzer = PfmVizzer(pfm) """ To align the mesh we generate with PfmVizzer with our ShaderTerrainMesh, BulletHeightfieldShape, and DynamicHeightfield, we need to set a negative scale on the Y axis. This reverses the winding order of the triangles in the mesh, which is why we choose PfmVizzer.MF_back Note that this does not accurately match up with our mesh because the interpolation differs. Still, PfmVizzer can be useful when inspecting, distorting, etc. PfmFiles. """ self.vizNP = vizzer.generate_vis_mesh(PfmVizzer.MF_back) self.vizNP.set_texture(loader.load_texture("maps/grid.rgb")) self.vizNP.reparent_to(render) if pfm == self.DHF or pfm == self.terrain_node.heightfield: self.vizNP.set_pos(self.terrain_pos.x, -self.terrain_pos.y, self.terrain_pos.z) self.vizNP.set_scale(self.terrain_scale.x, -self.terrain_scale.y, self.terrain_scale.z) def cardmaker_debug(self): for node in render2d.find_all_matches("pfm"): node.remove_node() for text in base.a2dBottomLeft.find_all_matches("*"): text.remove_node() width = 0.2 # render2d coordinates range: [-1..1] # Pseudo-normalize our PfmFile for better contrast. normalized_pfm = PfmFile(self.RotorPFM) max_p = LVector3() normalized_pfm.calc_min_max(LVector3(), max_p) normalized_pfm *= 1.0 / max_p.x # Put it in a texture tex = Texture() tex.load(normalized_pfm) # Apply the texture to a quad and put it in the lower left corner. cm = CardMaker("pfm") cm.set_frame(0, width, 0, width / normalized_pfm.get_x_size() * normalized_pfm.get_y_size()) card = base.render2d.attach_new_node(cm.generate()) card.set_pos(-1, 0, -1) card.set_texture(tex) # Display max value text self.genLabelText(-3, "Max value: {:.3f} == {:.2f}m".format(max_p.x, max_p.x * self.terrain_scale.z), parent="a2dBottomLeft")
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(0, -40, 10) base.cam.look_at(0, 0, 5) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(1, 1, -1)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) self.accept('1', self.do_shoot, [True]) self.accept('2', self.do_shoot, [False]) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') def do_shoot(self, ccd): # Get from/to points from mouse click pMouse = base.mouseWatcherNode.get_mouse() pFrom = LPoint3() pTo = LPoint3() base.camLens.extrude(pMouse, pFrom, pTo) pFrom = render.get_relative_point(base.cam, pFrom) pTo = render.get_relative_point(base.cam, pTo) # Calculate initial velocity v = pTo - pFrom v.normalize() v *= 10000.0 # Create bullet shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) body = BulletRigidBodyNode('Bullet') bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.node().set_mass(2.0) bodyNP.node().set_linear_velocity(v) bodyNP.set_pos(pFrom) bodyNP.set_collide_mask(BitMask32.all_on()) if ccd: bodyNP.node().set_ccd_motion_threshold(1e-7) bodyNP.node().set_ccd_swept_sphere_radius(0.50) self.world.attach(bodyNP.node()) # Remove the bullet again after 1 second taskMgr.do_method_later(1, self.do_remove, 'doRemove', extraArgs=[bodyNP], appendTask=True) def do_remove(self, bulletNP, task): self.world.remove(bulletNP.node()) return task.done # ____TASK___ def update(self, task): dt = globalClock.get_dt() self.world.do_physics(dt, 20, 1.0 / 180.0) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() self.worldNP = None def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground shape = BulletPlaneShape(LVector3(0, 0, 1), 0) body = BulletRigidBodyNode('Ground') bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.set_pos(0, 0, -1) bodyNP.set_collide_mask(BitMask32.all_on()) self.world.attach(bodyNP.node()) # Some boxes shape = BulletBoxShape(LVector3(0.5, 0.5, 0.5)) for i in range(10): for j in range(10): x = i - 5.0 y = 0.0 z = j - 0.5 body = BulletRigidBodyNode('Box-{}-{}'.format(i, j)) bodyNP = self.worldNP.attach_new_node(body) bodyNP.node().add_shape(shape) bodyNP.node().set_mass(1.0) bodyNP.node().set_deactivation_enabled(False) bodyNP.set_pos(x, y, z) bodyNP.set_collide_mask(BitMask32.all_on()) self.world.attach(bodyNP.node()) visNP = loader.load_model('models/box.egg') visNP.clear_model_nodes() visNP.reparent_to(bodyNP)
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(0, -40, 10) base.cam.look_at(0, 0, 0) # Light alight = AmbientLight('ambientLight') alight.set_color(LVector4(0.5, 0.5, 0.5, 1)) alightNP = render.attach_new_node(alight) dlight = DirectionalLight('directionalLight') dlight.set_direction(LVector3(5, 0, -2)) dlight.set_color(LVector4(0.7, 0.7, 0.7, 1)) dlightNP = render.attach_new_node(dlight) render.clear_light() render.set_light(alightNP) render.set_light(dlightNP) # Input self.accept('escape', self.do_exit) self.accept('r', self.do_reset) self.accept('f1', base.toggle_wireframe) self.accept('f2', base.toggle_texture) self.accept('f3', self.toggle_debug) self.accept('f5', self.do_screenshot) # Task taskMgr.add(self.update, 'updateWorld') # Physics self.setup() # _____HANDLER_____ def do_exit(self): self.cleanup() sys.exit(1) def do_reset(self): self.cleanup() self.setup() def toggle_debug(self): if self.debugNP.is_hidden(): self.debugNP.show() else: self.debugNP.hide() def do_screenshot(self): base.screenshot('Bullet') # ____TASK___ def update(self, task): dt = globalClock.get_dt() #dt *= 0.01 self.world.do_physics(dt, 10, 0.008) return task.cont def cleanup(self): self.world = None self.worldNP.remove_node() def setup(self): self.worldNP = render.attach_new_node('World') # World self.debugNP = self.worldNP.attach_new_node(BulletDebugNode('Debug')) self.debugNP.show() self.world = BulletWorld() self.world.set_gravity(LVector3(0, 0, -9.81)) self.world.set_debug_node(self.debugNP.node()) # Ground p0 = LPoint3(-20, -20, 0) p1 = LPoint3(-20, 20, 0) p2 = LPoint3(20, -20, 0) p3 = LPoint3(20, 20, 0) mesh = BulletTriangleMesh() mesh.add_triangle(p0, p1, p2) mesh.add_triangle(p1, p2, p3) shape = BulletTriangleMeshShape(mesh, dynamic=False) np = self.worldNP.attach_new_node(BulletRigidBodyNode('Mesh')) np.node().add_shape(shape) np.set_pos(0, 0, -2) np.set_collide_mask(BitMask32.all_on()) self.world.attach(np.node()) # Soft body world information info = self.world.get_world_info() info.set_air_density(1.2) info.set_water_density(0) info.set_water_offset(0) info.set_water_normal(LVector3(0, 0, 0)) # Softbody def make_SB(pos, hpr): #use this to construct a torus geom. #import torus #geom = torus.make_geom() geom = (loader.load_model('models/torus.egg').find_all_matches( '**/+GeomNode').get_path(0).node().modify_geom(0)) geomNode = GeomNode('') geomNode.add_geom(geom) node = BulletSoftBodyNode.make_tri_mesh(info, geom) node.link_geom(geomNode.modify_geom(0)) node.generate_bending_constraints(2) node.get_cfg().set_positions_solver_iterations(2) node.get_cfg().set_collision_flag( BulletSoftBodyConfig.CF_vertex_face_soft_soft, True) node.randomize_constraints() node.set_total_mass(50, True) softNP = self.worldNP.attach_new_node(node) softNP.set_pos(pos) softNP.set_hpr(hpr) self.world.attach(node) geomNP = softNP.attach_new_node(geomNode) make_SB(LPoint3(-3, 0, 4), (0, 0, 0)) make_SB(LPoint3(0, 0, 4), (0, 90, 90)) make_SB(LPoint3(3, 0, 4), (0, 0, 0))
class Demo(ShowBase): def __init__(self): ShowBase.__init__(self) # Bullet Physics Setup self.world = BulletWorld() self.world.setGravity(Vec3(0, 0, -9.81)) shape = BulletPlaneShape(Vec3(0, 0, 1), 1) node = BulletRigidBodyNode('Ground') node.addShape(shape) ground_np = self.render.attachNewNode(node) ground_np.setPos(0, 0, -60) # noinspection PyArgumentList self.world.attach(node) # Mouse and Camera Setup self.disable_mouse() self.camera.set_pos(20, -150, 5) self.follow_np = self.render.attach_new_node('Follow') # Instructions self.add_instruction('(p) to generate a Plane', 0.06) self.add_instruction('(b) to generate a Cuboid/Box', 0.12) self.add_instruction('(s) to generate a Spheroid', 0.18) self.add_instruction('(c) to generate a Cylinder', 0.24) self.add_instruction('(f1) to toggle wireframe', 0.30) self.add_instruction('(esc) to exit', 0.36) # Input self.accept('escape', sys.exit, [0]) self.accept('p', self.generate_plane) self.accept('b', self.generate_cuboid) self.accept('s', self.generate_spheroid) self.accept('c', self.generate_cylinder) self.accept('f1', self.toggle_wireframe) self.task_mgr.add(self.update, 'update') def add_instruction(self, text, y_pos, x_pos=0.06): _ = OnscreenText( text, pos=(x_pos, y_pos), fg=(1, 1, 1, 1), parent=self.a2dBottomLeft, scale=0.06, align=TextNode.ALeft ) def update(self, task): dt = globalClock.getDt() self.world.doPhysics(dt) self.camera.look_at(self.follow_np) return task.cont # noinspection PyArgumentList 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) # noinspection PyArgumentList def generate_cuboid(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)]) c = Cuboid(bounding_box) c.subdivide_dist(4) c.geom_store.normals_as_colors() geom_node = c.geom_node cuboid_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) cuboid_np.reparent_to(visual_np) self.follow_np.reparent_to(visual_np) visual_np.set_pos(pos) visual_np.set_hpr(hpr) # noinspection PyArgumentList 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) # noinspection PyArgumentList 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)