def update(self, time: float, dt: float): if self.move_normal is not None: for i, foot in enumerate(self.feet): #want_contact = .0 < math.sin(self.feet_rotation_movement * 10. + i / len(self.feet) * math.pi * 2) want_contact = .0 < foot.normal().dot(self.move_normal) if want_contact and not foot.world_joint: #foot_world_pos = self.object.position + foot.relative_pos.rotated(self.object.rotation) foot_world_pos = foot.object.position info = self.space.point_query_nearest( point=foot_world_pos + .1 * self.move_normal, max_distance=foot.radius*1.3, shape_filter=ShapeFilter(mask=ShapeFilter.ALL_MASKS() ^ 0b1), ) if info: # print("connect", foot.object, info.point, info.shape) foot.world_joint = pymunk.DampedSpring( a=foot.object.body, b=info.shape.body, anchor_a=(0, 0), anchor_b=info.shape.body.world_to_local(info.point), rest_length=0,#foot.radius, stiffness=10000, damping=10, ) self.space.add(foot.world_joint) foot.object.color = (1, 2, 1, 1) if not want_contact and foot.world_joint: # print("disconnect", foot.object) self.space.remove(foot.world_joint) foot.world_joint = None foot.object.color = (1, 1, 1, 1)
def begin_render(self, process: ProcessTimed, view: View) -> None: self._surface.fill((240, 240, 240)) # set background # get all objects in frame view_position = self._view_position view_world_size = self._view_world_size # BB(left, bottom, right, top) shapes = process.space.bb_query( pymunk.BB(view_position[0], view_position[1], view_position[0] + view_world_size[0], view_position[1] + view_world_size[1]), ShapeFilter()) entities = [ shape.body.entity for shape in shapes if shape.body is not None and shape.body.entity is not None ] # render objects in entity type order # TODO: make plane ordering more sensible render_planes = {} for entity in entities: plane = entity.type_ if plane in render_planes: render_planes[plane].append(entity) else: render_planes[plane] = [entity] for plane in sorted(render_planes.keys(), reverse=True): for entity in render_planes[plane]: entity.render(self)
def __init__(self, mask): self.body = Body(0, 0, Body.STATIC) self.body.position = 0, 0 self.body.angle = 0 self.body.velocity = 0, 0 self.body.angular_velocity = 0 #self.radius = 9 * CM_TO_PIXELS self.radius = 6 * CM_TO_PIXELS self.shape = Circle(self.body, self.radius) self.mask = mask self.shape.filter = ShapeFilter(mask=ShapeFilter.ALL_MASKS ^ (ANY_PUCK_MASK | ROBOT_MASK), categories=mask) if mask == ARC_LANDMARK_MASK: self.shape.color = 255, 0, 255 elif mask == ENTER_ARC_LANDMARK_MASK: self.shape.color = 0, 255, 0 elif mask == EXIT_ARC_LANDMARK_MASK: self.shape.color = 255, 0, 0 elif mask == POLE_LANDMARK_MASK: self.shape.color = 0, 0, 255 elif mask == BLAST_LANDMARK_MASK: self.shape.color = 255, 255, 255 else: sys.exit("Unknown landmark mask: " + str(mask))
def init_start_ranges(self): self.start_ranges = [] # A throwaway space, so that we can place the robot within it and to # ray-casts without the presence of any other objects. temp_robot = Robot() temp_space = pymunk.Space() temp_space.add(temp_robot.body, temp_robot.shape) # A throwaway scan, just to get the angles to be used. temp_scan = RangeScan(self.range_scan_sec_name) # For each sensor angle we will cast a ray in from the outer limit of # the sensor's range inward, keeping track of the position at which the # robot's body is encountered. for sensor_angle in temp_scan.angles: c = cos(temp_robot.body.angle + sensor_angle) s = sin(temp_robot.body.angle + sensor_angle) x1 = int(temp_robot.body.position.x + temp_scan.OUTER_RADIUS * c) y1 = int(temp_robot.body.position.y + temp_scan.OUTER_RADIUS * s) x2 = int(temp_robot.body.position.x) y2 = int(temp_robot.body.position.y) #mask = ShapeFilter.ALL_MASKS ^ 2 shape_filter = ShapeFilter(mask=ROBOT_MASK) query_info = temp_space.segment_query_first((x1, y1), (x2, y2), 1, \ shape_filter) assert query_info != None inward_range = query_info.alpha * temp_scan.OUTER_RADIUS self.start_ranges.append(temp_scan.OUTER_RADIUS - inward_range + 4)
def shapeFilter(categories, ignore=None, collide_with=None): __doc__ = """ arg:: ignore list(tuple) of collision categories that won't collide with this mask arg:: collide_with list(tuple) of collision categories that will collide with this mask Pass only one of described args""" if ignore is not None and collide_with is not None: raise ValueError('Pass only one of provided args: "ignore" "collide_with" ') if not all([c in COLLISION_CATEGORIES for c in categories]): raise ValueError(f'Some of categories from: {categories}' f' does not exist. Chose from {COLLISION_CATEGORIES.keys()}') # MASK mask = 4_294_967_295 # all 32-bit -> Collide with everything if ignore is not None: for x in ignore: mask -= COLLISION_CATEGORIES[x] elif collide_with is not None: mask = 0 for x in collide_with: mask += COLLISION_CATEGORIES[x] # CATEGORIES cat = 0 for c in categories: cat += COLLISION_CATEGORIES[c] # COMPLETE return ShapeFilter(categories=cat, mask=mask)
def __init__(self, mask, radius): self.body = Body(0, 0, Body.STATIC) self.body.position = 0, 0 self.body.angle = 0 self.body.velocity = 0, 0 self.body.angular_velocity = 0 self.shape = Circle(self.body, radius) self.mask = mask self.shape.filter = ShapeFilter(categories=mask) if mask == ARC_LANDMARK_MASK: self.shape.color = 0, 255, 0 elif mask == POLE_LANDMARK_MASK: self.shape.color = 0, 0, 255 elif mask == BLAST_LANDMARK_MASK: self.shape.color = 255, 0, 0 else: sys.exit("Unknown landmark mask: " + str(mask)) # The following is just to set the appropriate params to visualize below config = ConfigSingleton.get_instance() self.vis_range_max = \ config.getfloat("RangeScan:landmarks", "range_max") \ + radius self.vis_inside_radius = \ config.getfloat("LandmarkCircleController", "inside_radius") \ + radius self.vis_outside_radius = \ config.getfloat("LandmarkCircleController", "outside_radius") \ + radius
def _compute_detections(self) -> List[Detection]: position_body = self._anchor.pm_body.position angle_body = self._anchor.pm_body.angle points_hit = self.playground.space.point_query( position_body, self._max_range, shape_filter=ShapeFilter(ShapeFilter.ALL_MASKS())) # Filter points too close points_hit = [pt for pt in points_hit if pt.distance > self._min_range] # Filter Sensor shapes points_hit = [ pt for pt in points_hit if pt.shape and not pt.shape.sensor ] # Filter Invisible shapes if self._invisible_elements: points_hit = [ pt for pt in points_hit if pt.shape and self.playground.get_entity_from_shape(pt.shape) not in self._invisible_elements ] # Calculate angle detections: List[Detection] = [] for pt in points_hit: angle = (pt.point - position_body).angle - angle_body - math.pi angle = angle % (2 * math.pi) - math.pi if angle < -self._fov / 2 or angle > self._fov / 2: continue assert isinstance(pt.shape, Shape) element_colliding = self.playground.get_entity_from_shape( pm_shape=pt.shape) distance = pt.distance detection = Detection(entity=element_colliding, distance=distance, angle=angle) detections.append(detection) return detections
def __init__(self, kind, immobile=False): self.immobile = immobile self.mass = 0.1 # 0.1 kg # 0.1 meter radius, converted to pixels for display #self.radius = 0.1 * M_TO_PIXELS # Hockey puck radius = 23mm = 0.023 self.radius = 0.023 * M_TO_PIXELS # Plate puck radius = 12.8cm = 0.128 #self.radius = 0.128 * M_TO_PIXELS # moment of inertia for disk moment_inertia = moment_for_circle(self.mass, 0, self.radius) if not immobile: self.body = Body(self.mass, moment_inertia) else: self.body = Body(0, 0, Body.STATIC) self.body.position = 0, 0 self.body.angle = 0 self.body.velocity = 0, 0 self.body.angular_velocity = 0 self.shape = Circle(self.body, self.radius) self.kind = kind if kind == 0: self.shape.color = 200, 100, 100 self.shape.filter = ShapeFilter(categories=RED_PUCK_MASK) elif kind == 1: self.shape.color = 100, 200, 100 self.shape.filter = ShapeFilter(categories=GREEN_PUCK_MASK) elif kind == 2: self.shape.color = 100, 100, 200 self.shape.filter = ShapeFilter(categories=BLUE_PUCK_MASK) else: sys.exit("Unknown puck kind: " + kind) if immobile: self.shape.color = self.shape.color[0] / 3, self.shape.color[ 1] / 3, self.shape.color[2] / 3
def create_rect_border(self): env_b = self.env.static_body walls = [] walls.append(self.create_wall(0, 0, self.width, 0)) walls.append(self.create_wall(self.width, 0, self.width, self.height)) walls.append(self.create_wall(self.width, self.height, 0, self.height)) walls.append(self.create_wall(0, self.height, 0, 0)) for wall_shape in walls: wall_shape.filter = ShapeFilter(categories=WALL_MASK) self.env.add(walls)
def filterDelIgnore(filter_obj, categories): mask = filter_obj.mask if isinstance(categories, tuple): for x in categories: mask += COLLISION_CATEGORIES[x] elif isinstance(categories, int): mask += categories else: raise ValueError(f'Got {type(categories)} Accepts int or tuple') return ShapeFilter(group=filter_obj.group, categories=filter_obj.categories, mask=mask)
def __init__(self): self.mass = 1 # 1 kg # 0.1 meter radius, converted to pixels for display #self.radius = 0.05 * M_TO_PIXELS # Bupimo: 0.111 meter radius, converted to pixels for display self.radius = 0.111 * M_TO_PIXELS # moment of inertia for disk rob_I = moment_for_circle(self.mass, 0, self.radius) self.body = Body(self.mass, rob_I) self.body.position = 0, 0 self.body.angle = 0 self.body.velocity = 0, 0 self.body.angular_velocity = 0 """ self.shape = Circle(self.body, self.radius) self.shape.color = 127, 0, 255 # a pinkish blue self.shape.filter = ShapeFilter(categories = ROBOT_MASK) """ """ r = self.radius p = self.radius / 2.0 # Amount the wedge part pokes out. vertices = [(r+p, r), (-r/3, r), (-r, 0), (-r/3, -r), (r/3, -r) ] """ r = self.radius d = self.radius * 1.5 # Amount the wedge part pokes out. vertices = [(0, -r), (d, 0), (0, r)] # Now add the semicircular back part n = 3 angles = [pi/2 + i*(pi/n) for i in range(1, n)] for a in angles: vertices.append((r*cos(a), r*sin(a))) vertices = vertices[::-1] self.shape = Poly(self.body, vertices) self.shape.color = 127, 0, 255 # a pinkish blue self.shape.filter = ShapeFilter(categories = ROBOT_MASK) self.command = Twist()
def __init__(self): self.mass = 1 # 1 kg # e-puck: 0.037 meter radius, converted to pixels for display #self.radius = 3.7 * CM_TO_PIXELS # Bupimo: 9 cm radius, converted to pixels for display self.radius = 9 * CM_TO_PIXELS self.circular = False; if self.circular: rob_I = moment_for_circle(self.mass, 0, self.radius) self.body = Body(self.mass, rob_I) self.shape = Circle(self.body, self.radius) else: r = self.radius d = self.radius * 1.5 # Amount the wedge part pokes out. vertices = [(0, -r), (d, 0), (0, r)] #vertices = [(0, -r), # (d/4, 0), # (d/2, r)] # Now add the semicircular back part n = 5 angles = [pi/2 + i*(pi/n) for i in range(1, n)] for a in angles: vertices.append((r*cos(a), r*sin(a))) rob_I = moment_for_poly(self.mass, vertices) self.body = Body(self.mass, rob_I) self.shape = Poly(self.body, vertices) # EXPERIMENTAL: Add bristles to robot # rest_length = 100 # stiffness = 500 # damping = 10 # self.bristle_body = pymunk.DampedSpring(self.body, body, \ # (0,0), (0,0), rest_length, stiffness, damping) # self.sim.env.add(self.spring_body) self.body.position = 0, 0 self.body.angle = 0 self.body.velocity = 0, 0 self.body.angular_velocity = 0 self.shape.color = 0, 255, 0 self.shape.filter = ShapeFilter(categories = ROBOT_MASK) self.command = Twist()
def actuate(self) -> None: structure = self.structural_part.structure if structure is None: return if self.__angle_error is None: angle = self.__angle else: angle = self.__angle_error(self.__angle_error) if self.__thrust_error is None: thrust = self.__thrust else: thrust = self.__thrust_error(self.__thrust) pos = self.structural_part.position if self.__pos_error is not None: pos = Vec2d(self.__pos_error(pos.x), self.__pos_error(pos.y)) space = structure.space body = structure.body segment_end = Vec2d(1000, 0) segment_end.angle = self.structural_part.angle + angle collisions = space.segment_query(pos, pos + segment_end, 10, ShapeFilter()) first_collision = next( (col for col in collisions if col.shape.body is not body), None) if first_collision is None: return force = Vec2d(-thrust, 0) force.angle = self.structural_part.angle first_collision.shape.body.apply_force_at_world_point( force, first_collision.point) reaction_force = -self.__reaction_pc * force body.apply_force_at_world_point(reaction_force, pos)
def _calc_view_distance(self, space: Space, base_x: float, base_y: float) -> float: """ Calculate the distance between the base point, represented by base_x and base_y, and the furthest viewable object. If no object is in sight, return None. :param space: which holds the visible objects. :param base_x: x coordinate of the base point. :param base_y: y coordinate of the base point. :return: a floating point value representing the distance if object is viewable, else None. """ x, y = self._calc_ray_cast_point(base_x, base_y) query = space.segment_query_first((base_x, base_y), (x, y), 1, ShapeFilter()) if query: return -self.sensor_half_height + distance_between_points(base_x, base_y, query.point.x, query.point.y) else: return None
def read(self) -> float: structure = self.structural_part.structure if structure is None: return self.__max_dist space = structure.space body = structure.body pos = self.structural_part.position collisions = space.point_query(pos, self.__max_dist, ShapeFilter()) first_collision = next((col for col in collisions if col.shape.body is not body), None) if first_collision is None: return self.__max_dist return typingcast(float, first_collision.distance)
def compute(self, env, robot): """ Returns a Scan taken from the given environment and robot. """ if self.start_ranges == None: self.init_start_ranges() scan = RangeScan(self.range_scan_sec_name) for i in range(scan.NUMBER_POINTS): sensor_angle = scan.angles[i] c = cos(robot.body.angle + sensor_angle) s = sin(robot.body.angle + sensor_angle) start_range = self.start_ranges[i] x1 = int(robot.body.position.x + start_range * c) y1 = int(robot.body.position.y + start_range * s) x2 = int(robot.body.position.x + scan.OUTER_RADIUS * c) y2 = int(robot.body.position.y + scan.OUTER_RADIUS * s) #mask = ShapeFilter.ALL_MASKS ^ 2 shape_filter = ShapeFilter(mask=self.detection_mask) query_info = env.segment_query_first((x1, y1), (x2, y2), 1, \ shape_filter) if query_info == None or query_info.shape == None: scan.ranges.append(scan.RANGE_MAX) scan.masks.append(0) else: range_from_robot_centre = start_range + \ query_info.alpha * (scan.RANGE_MAX - start_range) object_mask = query_info.shape.filter.categories if object_mask & self.acceptance_mask == 0: # The detected shape is not accepted, we will treat # it as a wall. object_mask = WALL_MASK scan.ranges.append(range_from_robot_centre) scan.masks.append(object_mask) return scan
def read(self) -> float: structure = self.structural_part.structure if structure is None: return self.__max_dist space = structure.space body = structure.body pos = self.structural_part.position segment_end = Vec2d(self.__max_dist, 0) segment_end.angle = self.structural_part.angle + self.__angle collisions = space.segment_query(pos, pos + segment_end, 10, ShapeFilter()) first_collision = next((col for col in collisions if col.shape.body is not body), None) if first_collision is None: return self.__max_dist return typingcast(float, first_collision.point.get_distance(pos))
def compute(self, env, robot, visualize=False): """ Returns a Scan taken from the given environment and robot. """ scan = RangeScan(self.range_scan_sec_name, robot) for sensor_angle in scan.angles: c = cos(robot.body.angle + sensor_angle) s = sin(robot.body.angle + sensor_angle) x1 = int(robot.body.position.x + scan.INNER_RADIUS * c) y1 = int(robot.body.position.y + scan.INNER_RADIUS * s) x2 = int(robot.body.position.x + scan.OUTER_RADIUS * c) y2 = int(robot.body.position.y + scan.OUTER_RADIUS * s) #mask = ShapeFilter.ALL_MASKS ^ 2 shape_filter = ShapeFilter(mask=self.detection_mask) query_info = env.segment_query_first((x1, y1), (x2, y2), 1, \ shape_filter) if query_info == None or query_info.shape == None: scan.ranges.append(scan.RANGE_MAX) scan.masks.append(0) #if visualize: # pyglet.graphics.draw(2, pyglet.gl.GL_LINES, # ('v2f', (x1, y1, x2, y2)), # ('c3B', (127, 127, 127, 127, 127, 127))) else: value = query_info.alpha * scan.RANGE_MAX object_mask = query_info.shape.filter.categories if object_mask & self.acceptance_mask == 0: # The detected shape is not accepted, we will treat # it as a wall. object_mask = WALL_MASK scan.ranges.append(value) scan.masks.append(object_mask) if visualize: x2 = int(robot.body.position.x + \ (scan.INNER_RADIUS + value) * c) y2 = int(robot.body.position.y + \ (scan.INNER_RADIUS + value) * s) if object_mask == WALL_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (255, 255, 0, 255, 255, 0))) #pass elif object_mask == ROBOT_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (0, 255, 255, 0, 255, 255))) elif object_mask == BLAST_LANDMARK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (255, 100, 100, 255, 100, 100))) elif object_mask == POLE_LANDMARK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (100, 100, 255, 100, 100, 255))) elif object_mask == ARC_LANDMARK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (100, 255, 100, 100, 255, 100))) elif object_mask == RED_PUCK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (255, 0, 0, 255, 0, 0))) elif object_mask == GREEN_PUCK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (0, 255, 0, 0, 255, 0))) elif object_mask == BLUE_PUCK_MASK: pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2f', (x1, y1, x2, y2)), ('c3B', (0, 0, 255, 0, 0, 255))) #print scan.masks return scan
def __init__(self, calib_filename, detection_mask, acceptance_mask, frontal_only): config = ConfigSingleton.get_instance() self.min_distance = config.getfloat("PointSampleCam", "min_distance") self.max_distance = config.getfloat("PointSampleCam", "max_distance") self.max_abs_angle = config.getfloat("PointSampleCam", "max_abs_angle") # The detection mask is used to indicate all types of objects that # the sensor should be sensitive to. However, if a detected object # doesn't also match the acceptance mask then it will be treated as # a wall. self.detection_mask = detection_mask self.acceptance_mask = acceptance_mask self.calib_array = np.loadtxt(calib_filename, delimiter=',') self.calib_array[:, 2] *= CM_TO_PIXELS self.calib_array[:, 3] *= CM_TO_PIXELS # We will also store within calib_array the following additional # quantities derived from (xr, yr) so that we don't need to compute # these again. # angle of (xr, yr) w.r.t. to X_R axis --- atan2(yr, xr) # length of (xr, yr) --- sqrt(xr*xr + yr*yr) n_rows = self.calib_array.shape[0] # Add the two extra columns self.calib_array = np.append(self.calib_array, np.zeros((n_rows, 2)), axis=1) for i in range(n_rows): (xr, yr) = self.calib_array[i, 2], self.calib_array[i, 3] self.calib_array[i, 4] = atan2(yr, xr) self.calib_array[i, 5] = sqrt(xr * xr + yr * yr) if frontal_only: # Delete all rows with distance outside of [min_distance, # max_distance] and angle outside of [-max_abs_angle, max_abs_angle] delete_indices = [] for i in range(n_rows): angle = self.calib_array[i, 4] dist = self.calib_array[i, 5] if fabs(dist) < self.min_distance: delete_indices.append(i) if fabs(dist) > self.max_distance: delete_indices.append(i) if fabs(angle) > self.max_abs_angle: delete_indices.append(i) self.calib_array = np.delete(self.calib_array, delete_indices, axis=0) # We also pre-compute the indices of the neighbours for each pixel. self.neighbour_array = [] n_rows = self.calib_array.shape[0] for i in range(n_rows): #(ixi, iyi) = self.calib_array[i,0], self.calib_array[i,1] (ixr, iyr) = self.calib_array[i, 2], self.calib_array[i, 3] nghbrs = [] for j in range(i + 1, n_rows): #(jxi, jyi) = self.calib_array[j,0], self.calib_array[j,1] (jxr, jyr) = self.calib_array[j, 2], self.calib_array[j, 3] """ Determining neighbourhood based on 8-adjacency dx = ixi - jxi dy = iyi - jyi ij_dist = sqrt(dx*dx + dy*dy) if ij_dist <= sqrt(2) + 0.01: nghbrs.append(j) """ # Determining neighbourhood based on a threshold distance dx = ixr - jxr dy = iyr - jyr ij_dist = sqrt(dx * dx + dy * dy) if ij_dist <= 50: nghbrs.append(j) self.neighbour_array.append(nghbrs) self.shape_filter = ShapeFilter(mask=self.detection_mask)
def create_wall(self, x1, y1, x2, y2): env_b = self.env.static_body wall_shape = pymunk.Segment(env_b, Vec2d(x1,y1), Vec2d(x2,y2), \ self.wall_thickness) wall_shape.filter = ShapeFilter(categories=WALL_MASK) return wall_shape
def __init__(self, kind, puck_shape, immobile=False): self.immobile = immobile # self.mass = 0.1 # 0.1 kg # Create a massless body with 0 moment of inertia. Pymunk will figure # out the mass and moment of inertia based on the density (set below) # when the body and shape are both added to the space. if not immobile: self.body = Body(0, 0) else: self.body = Body(0, 0, Body.STATIC) if puck_shape == "CIRCLE": # Hockey puck radius = 23mm = 0.023 #self.radius = 2.3 * CM_TO_PIXELS # Double hockey puck radius self.radius = 4.6 * CM_TO_PIXELS # Arbitrary radius #self.radius = 0.07 * M_TO_PIXELS # Plate puck radius = 12.8cm = 0.128 #self.radius = 0.128 * M_TO_PIXELS # Objects from Gauci et al paper: 0.05 meter radius, converted to # pixels for display # self.radius = 12 * CM_TO_PIXELS self.shape = Circle(self.body, self.radius) elif puck_shape == "RANDOM_CIRCLES": self.radius = randint(1, 5) * CM_TO_PIXELS self.shape = Circle(self.body, self.radius) elif puck_shape == "RECTANGLE": length = 100 thickness = 5 a = Vec2d(0, 0) b = Vec2d(length, 0) self.shape = Segment(self.body, a, b, thickness) self.radius = length / 2 elif puck_shape == "RANDOM_RECTANGLES": length = randint(10, 150) thickness = randint(4, 10) a = Vec2d(0, 0) b = Vec2d(length, 0) self.shape = Segment(self.body, a, b, thickness) self.radius = length / 2 radiusForDensity = 2.3 * CM_TO_PIXELS # hockey puck radius self.shape.density = 0.1 / (pi * radiusForDensity**2) self.body.position = 0, 0 self.body.angle = uniform(-pi, pi) self.body.velocity = 0, 0 self.body.angular_velocity = 0 self.kind = kind if kind == 0: self.shape.color = 200, 100, 100 self.shape.filter = ShapeFilter(categories=RED_PUCK_MASK) elif kind == 1: self.shape.color = 100, 200, 100 self.shape.filter = ShapeFilter(categories=GREEN_PUCK_MASK) elif kind == 2: self.shape.color = 100, 100, 200 self.shape.filter = ShapeFilter(categories=BLUE_PUCK_MASK) else: sys.exit("Unknown puck kind: " + kind) if immobile: self.shape.color = self.shape.color[0] / 3, self.shape.color[ 1] / 3, self.shape.color[2] / 3