def piece_contour(pid): offset = Point(width * (pid % nx) + width / 2, height * (pid // nx) + height / 2) contour = [ edges[get_epid(pid, "N")].stretch(width, height).translate(offset), edges[get_epid(pid, "E")].stretch( height, width).rotate().translate(offset + Point(width, 0)), edges[get_epid(pid, "S")].stretch( width, height).translate(offset + Point(0, height)).reverse(), edges[get_epid(pid, "W")].stretch( height, width).rotate().translate(offset).reverse() ] return list( itertools.chain.from_iterable( [edge.evaluate(10) for edge in contour]))
def closest_neighbour_pid(pid, point, width, height, nx): point = point.dot(Point(height / width, 1)) if abs(point.x) < point.y: # North return pid + nx elif abs(point.x) < -point.y: # South return pid - nx elif abs(point.y) < point.x: # East return pid + 1 else: # West return pid - 1
def make_jigsaw_cut(image_width, image_height, nx, ny): num_edges = 2 * nx * ny - nx - ny num_pieces = nx * ny nv = (nx - 1) * ny width = image_width // nx height = image_height // ny edges = make_random_edges(num_edges) def get_epid(pid, orientation): if orientation == "N" and pid >= nx: return pid - nx + nv elif orientation == "W" and pid % nx: return pid - (pid // nx) - 1 elif orientation == "S" and pid < num_pieces - nx: return pid + nv elif orientation == "E" and pid % nx != nx - 1: return pid - (pid // nx) return -1 def piece_contour(pid): offset = Point(width * (pid % nx) + width / 2, height * (pid // nx) + height / 2) contour = [ edges[get_epid(pid, "N")].stretch(width, height).translate(offset), edges[get_epid(pid, "E")].stretch( height, width).rotate().translate(offset + Point(width, 0)), edges[get_epid(pid, "S")].stretch( width, height).translate(offset + Point(0, height)).reverse(), edges[get_epid(pid, "W")].stretch( height, width).rotate().translate(offset).reverse() ] return list( itertools.chain.from_iterable( [edge.evaluate(10) for edge in contour])) pieces = { pid: Piece(pid=pid, polygon={pid: (polygon := piece_contour(pid))}, bounding_box=bounding_box(polygon), origin=(origin := Point(width * (pid % nx), height * (pid // nx))), neighbours=create_neighbours(pid, num_pieces, nx), members={pid}, width=width, height=height, x=random.randint(0, int(image_width * 2)) - origin.x, y=random.randint(0, int(image_height * 2)) - origin.y, z=pid) for pid in tqdm(range(num_pieces), desc="Designing pieces") }
def move_and_snap(self, pid, dx, dy): piece = self.pieces[pid] self.quadtree.remove(piece, piece.bbox) piece.x += dx piece.y += dy for neighbour_pid in piece.neighbours: if not self.trays.is_visible(neighbour_pid): continue neighbour = self.pieces[neighbour_pid] dist = Point.dist(Point(piece.x, piece.y), Point(neighbour.x, neighbour.y)) if dist < self.snap_distance: piece.x = neighbour.x piece.y = neighbour.y neighbour.z = piece.z self.dispatch_event('on_snap_piece_to_position', pid, piece.x, piece.y, piece.z) self._merge_pieces(piece, neighbour) self.quadtree.insert(piece, piece.bbox)
def contains(self, point: Point, nx: int) -> bool: point = point - self.position pid = point_to_pid(point, nx, self.width, self.height) offset = Point(self.width * (1 + pid % nx), self.height * (1 + pid // nx)) neighbour = closest_neighbour_pid(pid, point - offset, self.width, self.height, nx) if pid in self.members and neighbour in self.members: return True elif pid in self.members and neighbour not in self.members: return point_in_polygon(point, self.polygon[pid]) elif pid not in self.members and neighbour in self.members: return point_in_polygon(point, self.polygon[neighbour]) else: return False
def position(self): return Point(self.x, self.y)
def _pieces_at_location(self, x, y): for piece in self.quadtree.intersect(bbox=(x, y, x, y)): if self.trays.is_visible(piece.pid) and piece.contains( Point(x, y), self.nx): yield piece