class CarPhys(PhysColleague): def __init__(self, mediator, car_props, tuning, players): PhysColleague.__init__(self, mediator) self.pnode = self.vehicle = self.__track_phys = self.coll_mesh = \ self.max_speed = self.friction_slip = \ self.friction_slip_rear = self.cfg = None self.turbo = False self._tuning = tuning self._players = players self.roll_influence = [] self.ai_meshes = [] self.curr_speed_mul = 1.0 self.roll_influence_k = self.friction_slip_k = 1.0 self.__prev_speed = 0 self.__last_drift_time = 0 self.__finds = {} # cache for find's results self.__whl2flytime = {} self.cprops = car_props self._load_phys() self.__set_collision_mesh() self.__set_ai_meshes() self.__set_phys_node() self.__set_vehicle() self.__set_wheels() self.eng.attach_obs(self.on_end_frame) def _load_phys(self): ppath = self.cprops.race_props.season_props.gameprops.phys_path fpath = ppath % self.cprops.name with open(fpath) as phys_file: self.cfg = load(phys_file) # they may be changed by drivers and tuning self.cfg['max_speed'] = self.get_speed() self.cfg['friction_slip'] = self.get_friction_static()[0] self.cfg['friction_slip_rear'] = self.get_friction_static()[1] self.cfg['roll_influence'] = self.get_roll_influence_static() self.friction_slip = self.cfg['friction_slip'] self.friction_slip_rear = self.cfg['friction_slip_rear'] self.__log_props() set_a = lambda field: setattr(self, field, self.cfg[field]) list(map(set_a, self.cfg.keys())) def __log_props(self, starting=True): s_s = self.cfg['max_speed'] if starting else self.max_speed s_f = self.cfg['friction_slip'] if starting else \ self.get_friction_static()[0] s_fr = self.cfg['friction_slip_rear'] if starting else \ self.get_friction_static()[1] s_r = self.cfg['roll_influence'] if starting else \ self.get_roll_influence_static() log_info = [('speed', self.cprops.name, round(s_s, 2), self.cprops.driver_engine), ('friction 0', self.cprops.name, round(s_f[0], 2), self.cprops.driver_tires), ('friction 1', self.cprops.name, round(s_f[1], 2), self.cprops.driver_tires), ('friction_rear 0', self.cprops.name, round(s_fr[0], 2), self.cprops.driver_tires), ('friction_rear 1', self.cprops.name, round(s_fr[1], 2), self.cprops.driver_tires), ('roll min', self.cprops.name, round(s_r[0], 2), self.cprops.driver_suspensions), ('roll max', self.cprops.name, round(s_r[1], 2), self.cprops.driver_suspensions)] for l_i in log_info: info('%s %s: %s (%s)' % l_i) def __set_collision_mesh(self): fpath = self.cprops.race_props.coll_path % self.cprops.name try: self.coll_mesh = self.eng.load_model(fpath) except OSError: # new cars don't have collision meshes self.coll_mesh = self.eng.load_model( fpath.replace('capsule', 'car')) # chassis_shape = BulletConvexHullShape() # for geom in self.eng.lib.find_geoms( # self.coll_mesh, self.cprops.race_props.coll_name): # chassis_shape.add_geom(geom.node().get_geom(0), # geom.get_transform()) # self.mediator.gfx.nodepath.get_node().add_shape(chassis_shape) chassis_shape = BulletBoxShape(tuple(self.cfg['box_size'])) boxpos = self.cfg['box_pos'] boxpos[2] += self.cfg['center_mass_offset'] pos = TransformState.makePos(tuple(boxpos)) self.mediator.gfx.nodepath.p3dnode.add_shape(chassis_shape, pos) car_names = [player.car for player in self._players] car_idx = car_names.index(self.cprops.name) car_bit = BitMask32.bit(BitMasks.car(car_idx)) ghost_bit = BitMask32.bit(BitMasks.ghost) track_bit = BitMask32.bit(BitMasks.track_merged) mask = car_bit | ghost_bit | track_bit self.mediator.gfx.nodepath.set_collide_mask(mask) def __set_ai_meshes(self): return # if we attach these meshes (or even only one mesh, box, sphere, # whatever) then the collision between the goal and the vehicle doesn't # work properly h = .5 boxsz = self.cfg['box_size'] hs = [] hs += [ h / 2 + boxsz[2] * 2 + self.cfg['box_pos'][2] + self.cfg['center_mass_offset'] ] hs += [ -h / 2 - boxsz[2] * 2 + self.cfg['box_pos'][2] + self.cfg['center_mass_offset'] ] for _h in hs: shape = BulletBoxShape((boxsz[0], boxsz[1], h)) ghost = GhostNode('Vehicle') pos = TransformState.makePos((0, 0, _h)) ghost.node.addShape(shape, pos) self.ai_meshes += [self.eng.attach_node(ghost.node)] car_names = self.cprops.race_props.season_props.car_names car_idx = car_names.index(self.cprops.name) car_bit = BitMask32.bit(BitMasks.car(car_idx)) ghost_bit = BitMask32.bit(BitMasks.ghost) mask = car_bit | ghost_bit self.ai_meshes[-1].set_collide_mask(mask) self.eng.phys_mgr.attach_ghost(ghost.node) def __set_phys_node(self): self.pnode = self.mediator.gfx.nodepath.p3dnode self.pnode.set_mass(self.mass) # default 0 self.pnode.set_deactivation_enabled(False) self.eng.phys_mgr.attach_rigid_body(self.pnode) self.eng.phys_mgr.add_collision_obj(self.pnode) def __set_vehicle(self): self.vehicle = BulletVehicle(self.eng.phys_mgr.root._wld, self.pnode) # access to a protected member self.vehicle.set_coordinate_system(ZUp) self.vehicle.set_pitch_control(self.pitch_control) tuning = self.vehicle.get_tuning() tuning.set_suspension_compression(self.suspension_compression) # default .83 tuning.set_suspension_damping(self.suspension_damping) # default .88 self.eng.phys_mgr.attach_vehicle(self.vehicle) def __set_wheels(self): wheels = self.mediator.gfx.wheels f_bounds = wheels['fr'].tight_bounds f_radius = (f_bounds[1][2] - f_bounds[0][2]) / 2.0 + .01 r_bounds = wheels['rr'].tight_bounds r_radius = (r_bounds[1][2] - r_bounds[0][2]) / 2.0 + .01 wheel_names = self.cprops.race_props.wheel_names ffr = self.coll_mesh.find('**/' + wheel_names.frontrear.fr) ffl = self.coll_mesh.find('**/' + wheel_names.frontrear.fl) rrr = self.coll_mesh.find('**/' + wheel_names.frontrear.rr) rrl = self.coll_mesh.find('**/' + wheel_names.frontrear.rl) meth = self.coll_mesh.find fr_node = ffr if ffr else meth('**/' + wheel_names.both.fr) fl_node = ffl if ffl else meth('**/' + wheel_names.both.fl) rr_node = rrr if rrr else meth('**/' + wheel_names.both.rr) rl_node = rrl if rrl else meth('**/' + wheel_names.both.rl) if not fr_node: # new cars fr_node = meth('**/w_fr') fl_node = meth('**/w_fl') rr_node = meth('**/w_rr') rl_node = meth('**/w_rl') offset = self.cfg['center_mass_offset'] fr_pos = fr_node.get_pos() + (0, 0, f_radius + offset) fl_pos = fl_node.get_pos() + (0, 0, f_radius + offset) rr_pos = rr_node.get_pos() + (0, 0, r_radius + offset) rl_pos = rl_node.get_pos() + (0, 0, r_radius + offset) wheels_info = [(fr_pos, True, wheels['fr'], f_radius), (fl_pos, True, wheels['fl'], f_radius), (rr_pos, False, wheels['rr'], r_radius), (rl_pos, False, wheels['rl'], r_radius)] for i, (pos, front, nodepath, radius) in enumerate(wheels_info): self.__add_wheel(pos, front, nodepath.p3dnode, radius, i) def __add_wheel(self, pos, is_front, node, radius, i): whl = self.vehicle.create_wheel() whl.set_node(node) whl.set_chassis_connection_point_cs(LPoint3f(*pos)) whl.set_front_wheel(is_front) whl.set_wheel_direction_cs((0, 0, -1)) whl.set_wheel_axle_cs((1, 0, 0)) whl.set_wheel_radius(radius) whl.set_suspension_stiffness(self.suspension_stiffness[0]) # default 5.88 whl.set_wheels_damping_relaxation(self.wheels_damping_relaxation[0]) # default .88 whl.set_wheels_damping_compression(self.wheels_damping_compression[0]) # default .83 idx = 0 if is_front else 1 whl.set_friction_slip(self.get_friction_static()[idx][0]) # default 10.5 # friction slip high -> more adherence whl.set_roll_influence(self.roll_influence[0]) # low -> more stability # default .1 whl.set_max_suspension_force(self.max_suspension_force) # default 6000 whl.set_max_suspension_travel_cm(self.max_suspension_travel_cm) # default 500 whl.set_skid_info(self.skid_info) # default 0 self.__whl2flytime[i] = 0 @property def lateral_force(self): vel = self.vehicle.get_chassis().get_linear_velocity() rot_mat = Mat4() rot_mat.setRotateMat(-90, (0, 0, 1)) car_lat = rot_mat.xformVec(self.mediator.logic.car_vec._vec) # access to a protected member proj_frc = vel.project(car_lat) return proj_frc.length() @property def lin_vel(self): return self.vehicle.get_chassis().get_linear_velocity().length() * 3.6 @property def is_flying(self): # no need to be cached rays = [whl.get_raycast_info() for whl in self.vehicle.get_wheels()] return not any(ray.is_in_contact() for ray in rays) @property def is_drifting(self): return self.lateral_force > 4.0 @property def last_drift_time(self): return self.__last_drift_time @property def prev_speed(self): return self.__prev_speed @property def prev_speed_ratio(self): return max(0, min(1.0, self.prev_speed / self.max_speed)) def on_end_frame(self): self.__prev_speed = self.speed @property def speed(self): if self.mediator.fsm.getCurrentOrNextState() == 'Countdown': return 0 # getCurrentSpeedKmHour returns odd values otherwise return self.vehicle.get_current_speed_km_hour() @property def speed_ratio(self): return max(0, min(1.0, self.speed / self.max_speed)) @property def lin_vel_ratio(self): return max(0, min(1.0, self.lin_vel / self.max_speed)) def set_forces(self, eng_frc, brake_frc, brk_ratio, steering): idx = 1 if self.mediator.logic.is_drifting else 0 eng_frc_ratio = self.engine_acc_frc_ratio[idx] self.vehicle.set_steering_value(steering, 0) self.vehicle.set_steering_value(steering, 1) self.vehicle.apply_engine_force(eng_frc * eng_frc_ratio, 0) self.vehicle.apply_engine_force(eng_frc * eng_frc_ratio, 1) self.vehicle.apply_engine_force(eng_frc * (1 - eng_frc_ratio), 2) self.vehicle.apply_engine_force(eng_frc * (1 - eng_frc_ratio), 3) self.vehicle.set_brake((1 - brk_ratio) * brake_frc, 2) self.vehicle.set_brake((1 - brk_ratio) * brake_frc, 3) self.vehicle.set_brake(brk_ratio * brake_frc, 0) self.vehicle.set_brake(brk_ratio * brake_frc, 1) def update_car_props(self): wheels = zip(self.vehicle.get_wheels(), range(4)) speeds = list(map(lambda whi: self.__update_whl_props(*whi), wheels)) speeds = [speed for speed in speeds if speed] if self.is_drifting: self.__last_drift_time = self.eng.curr_time self.curr_speed_mul = (sum(speeds) / len(speeds)) if speeds else 1.0 def __update_whl_props(self, whl, i): susp_min = self.suspension_stiffness[0] susp_max = self.suspension_stiffness[1] susp_diff = susp_max - susp_min whl.set_suspension_stiffness(susp_min + self.speed_ratio * susp_diff) relax_min = self.wheels_damping_relaxation[0] relax_max = self.wheels_damping_relaxation[1] relax_diff = relax_max - relax_min relax = relax_min + self.speed_ratio * relax_diff whl.set_wheels_damping_relaxation(relax) compr_min = self.wheels_damping_compression[0] compr_max = self.wheels_damping_compression[1] compr_diff = compr_max - compr_min compr = compr_min + self.speed_ratio * compr_diff whl.set_wheels_damping_compression(compr) roll_infl_min = self.roll_influence[0] roll_infl_max = self.roll_influence[1] roll_infl_diff = roll_infl_max - roll_infl_min roll_infl = roll_infl_min + self.speed_ratio * roll_infl_diff whl.set_roll_influence(self.roll_influence_k * roll_infl) contact_pt = whl.get_raycast_info().getContactPointWs() gnd_name = self.gnd_name(contact_pt) if not gnd_name or gnd_name in ['Vehicle', 'Wall', 'Respawn']: return if gnd_name not in self.__finds: gnd = self.cprops.race.track.phys.model.find('**/' + gnd_name) self.__finds[gnd_name] = gnd gfx_node = self.__finds[gnd_name] if not gfx_node: print('ground error', gnd_name) return fric = 1.0 if gfx_node.has_tag('friction'): fric = float(gfx_node.get_tag('friction')) if not whl.get_raycast_info().is_in_contact(): self.__whl2flytime[i] = self.eng.curr_time gnd_time = self.eng.curr_time - self.__whl2flytime[i] gnd_recovery_time = .2 if whl.is_front_wheel() else .1 gnd_factor = min(1, gnd_time / gnd_recovery_time) idx = 0 if whl.is_front_wheel() else 1 turbo_factor = 1.24 if self.turbo else 1.0 whl.setFrictionSlip(self.get_friction()[idx] * fric * gnd_factor * turbo_factor) if gfx_node.has_tag('speed'): return float(gfx_node.get_tag('speed')) @property def gnd_names(self): # no need to be cached whls = self.vehicle.get_wheels() pos = list( map(lambda whl: whl.get_raycast_info().get_contact_point_ws(), whls)) return list(map(self.gnd_name, pos)) @staticmethod def gnd_name(pos): top, bottom = pos + (0, 0, 20), pos + (0, 0, -20) result = CarPhys.eng.phys_mgr.ray_test_closest(bottom, top) ground = result.get_node() return ground.get_name() if ground else '' @staticmethod def gnd_height(pos): # this should be a method of the track top, bottom = pos + (0, 0, 20), pos + (0, 0, -20) result = CarPhys.eng.phys_mgr.ray_test_closest(bottom, top) hit_pos = result.get_hit_pos() return hit_pos.z if hit_pos else None def apply_damage(self, reset=False): # wheels = self.vehicle.get_wheels() if reset: self.max_speed = self.get_speed() self.friction_slip_k = 1.0 self.roll_influence_k = 1.0 else: self.max_speed *= .95 self.friction_slip_k *= .95 self.roll_influence_k *= 1.05 # map(lambda whl: whl.set_friction_slip(self.friction_slip), wheels) self.__log_props(False) def get_speed(self): return self.cfg['max_speed'] * (1 + .01 * self.cprops.driver_engine) def get_friction(self): k = (1 + .01 * self.cprops.driver_tires) return self.friction_slip[0] * k, self.friction_slip_rear[0] * k def get_roll_influence_static(self): min_r = self.cfg['roll_influence'][0] max_r = self.cfg['roll_influence'][1] k = 1 + .01 * self.cprops.driver_suspensions return [min_r * k, max_r * k] def get_friction_static(self): k = 1 + .01 * self.cprops.driver_tires fstr = 'friction_slip' return [(self.cfg[fstr][0] * k, self.cfg[fstr][1] * k), (self.cfg[fstr + '_rear'][0] * k, self.cfg[fstr + '_rear'][1] * k)] def get_roll_influence(self): min_r = self.cfg['roll_influence'][0] max_r = self.cfg['roll_influence'][1] diff_r = max_r - min_r curr_r = min_r + self.speed_ratio * diff_r return curr_r * (1 + .01 * self.cprops.driver_suspensions) def rotate(self): self.pnode.apply_torque((0, 0, 80000)) self.mediator.logic.applied_torque = True def destroy(self): self.eng.detach_obs(self.on_end_frame) self.eng.phys_mgr.remove_vehicle(self.vehicle) self.pnode = self.vehicle = self.__finds = self.__track_phys = \ self.coll_mesh = None PhysColleague.destroy(self)
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)