def __init__(self, person): super().__init__() self.person = person self.sub_node_map = {} self.label_string = person.name + '\n' + person.family_search_id self.label_box = AxisAlignedRectangle() self.bounding_box = None
def calculate_bounding_rectangle(self, targets=True): rect = AxisAlignedRectangle() rect.min_point = self.target_position if targets else self.position rect.max_point = self.target_position if targets else self.position rect.min_point -= Vector(0.5, 0.5) rect.max_point += Vector(0.5, 0.5) return rect
def render_graph(self, draw, image, font, person_subset): image_rect = AxisAlignedRectangle( Vector(0.0, 0.0), Vector(float(image.width), float(image.height))) world_rect = self.bounding_box.Copy() world_rect.ExpandToMatchAspectRatioOf(image_rect) for node in self.all_nodes(): node.render_edges(draw, font, image_rect, world_rect) for node in self.all_nodes(): node.render_label_box(draw, font, image_rect, world_rect, person_subset)
def DrawPreviewImage(self, graph, preview_image, rect, anti_alias=False, thickness=2.0): preview_image_rect = AxisAlignedRectangle( Vector(0.0, 0.0), Vector(float(preview_image.width), float(preview_image.height))) preview_draw = ImageDraw.Draw(preview_image) if anti_alias: for i in range(preview_image.width): for j in range(preview_image.height): point = Vector(float(i), float(preview_image.height - 1 - j)) smallest_distance = 999999.0 for edge in graph.GenerateEdgeSegments(): edge = rect.Map(edge, preview_image_rect) distance = edge.Distance(point) if distance < smallest_distance: smallest_distance = distance if smallest_distance <= thickness: t = smallest_distance / thickness current_pixel = preview_image.getpixel((i, j)) target_pixel = (0, 0, 0, 255) pixel = ( int( round( float(current_pixel[0]) * t + float(target_pixel[0]) * (1.0 - t))), int( round( float(current_pixel[1]) * t + float(target_pixel[1]) * (1.0 - t))), int( round( float(current_pixel[2]) * t + float(target_pixel[2]) * (1.0 - t))), int( round( float(current_pixel[3]) * t + float(target_pixel[3]) * (1.0 - t))), ) preview_draw.point((i, j), fill=pixel) else: for edge in graph.GenerateEdgeSegments(): edge = rect.Map(edge, preview_image_rect) edge = [ int(edge.point_a.x), preview_image.height - 1 - int(edge.point_a.y), int(edge.point_b.x), preview_image.height - 1 - int(edge.point_b.y) ] preview_draw.line(edge, fill=(0, 0, 0, 255), width=2)
def _render_text(self, font, text, rect): total_width = 0.0 for char in text: width = glutStrokeWidth(font, ord(char)) total_width += float(width) text_rect = AxisAlignedRectangle() text_rect.min_point.x = 0.0 text_rect.max_point.x = total_width text_rect.min_point.y = 0.0 text_rect.max_point.y = 119.05 original_height = text_rect.Height() text_rect.ExpandToMatchAspectRatioOf(rect) height = text_rect.Height() scale = rect.Width() / text_rect.Width() glPushMatrix() try: glTranslatef( rect.min_point.x, rect.min_point.y + (height - original_height) * 0.5 * scale, 0.0) glScalef(scale, scale, 1.0) for char in text: glutStrokeCharacter(font, ord(char)) finally: glPopMatrix()
def _recalc_projection_rect(self): viewport = glGetIntegerv(GL_VIEWPORT) viewport_rect = AxisAlignedRectangle() viewport_rect.min_point.x = 0.0 viewport_rect.min_point.y = 0.0 viewport_rect.max_point.x = float(viewport[2]) viewport_rect.max_point.y = float(viewport[3]) self.proj_rect = self.root_node.calculate_subtree_bounding_rectangle( targets=True) self.proj_rect.Scale(1.1) self.proj_rect.ExpandToMatchAspectRatioOf(viewport_rect)
def __init__(self, parent): gl_format = QtOpenGL.QGLFormat() gl_format.setAlpha(True) gl_format.setDepth(False) gl_format.setDoubleBuffer(True) super().__init__(gl_format, parent) self.root_node = None self.proj_rect = None self.anim_proj_rect = AxisAlignedRectangle() self.animation_timer = QtCore.QTimer() self.animation_timer.start(1) self.animation_timer.timeout.connect(self.animation_tick) self.auto_simplify = False self.dragPos = None self.dragging = False
def Transform(self, object): from math2d_polygon import Polygon from math2d_region import Region, SubRegion from math2d_aa_rect import AxisAlignedRectangle if isinstance(object, Vector): return self.linear_transform.Transform(object) + self.translation elif isinstance(object, AffineTransform): transform = AffineTransform() transform.linear_transform.x_axis = self.linear_transform.Transform( object.linear_transform.x_axis) transform.linear_transform.y_axis = self.linear_transform.Transform( object.linear_transform.y_axis) transform.translation = self.Transform(object.translation) return transform elif isinstance(object, LineSegment): return LineSegment(self.Transform(object.point_a), self.Transform(object.point_b)) elif isinstance(object, Polygon): polygon = Polygon() for vertex in object.vertex_list: polygon.vertex_list.append(self.Transform(vertex)) return polygon elif isinstance(object, Region): region = Region() for sub_region in object.sub_region_list: region.sub_region_list.append(self.Transform(sub_region)) return region elif isinstance(object, SubRegion): sub_region = SubRegion() sub_region.polygon = self.Transform(object.polygon) for hole in object.hole_list: sub_region.hole_list.append(self.Transform(hole)) return sub_region elif isinstance(object, AxisAlignedRectangle): return AxisAlignedRectangle( min_point=self.Transform(object.min_point), max_point=self.Transform(object.max_point))
def Generate(self, puzzle_folder, calc_solution, preview=None): # Go calculate all the needed symmetries of the cut-shape. # We need enough to generate the symmetry group of the shape, but we # also want those symmetries that are convenient for the user too. print('Generating symmetries...') for cut_region in self.cut_region_list: cut_region.GenerateSymmetryList() # Add the cut-regions to a planar graph. print('Adding cut regions...') graph = PlanarGraph() for cut_region in self.cut_region_list: graph.Add(cut_region.region) # For debugging purposes... if preview == 'graph_pre_cut': DebugDraw(graph) # Make sure that all cut-regions are tessellated. This lets us do point tests against the regions. print('Tessellating cut regions...') for cut_region in self.cut_region_list: cut_region.region.Tessellate() # Now comes the process of cutting the puzzle. The only sure algorithm I can think # of would use a stabilizer chain to enumerate all elements of the desired group, but # that is completely impractical. The idea here is that if after max_count iteration # we have not added any more edges to our graph, then we can be reasonably sure that # there are no more cuts to be made. print('Generating cuts...') random.seed(0) max_count = 20 count = 0 while count < max_count: # Copy all edges of the graph contained in and not on the border of a randomly chosen region. i = random.randint(0, len(self.cut_region_list) - 1) cut_region = self.cut_region_list[i] region = cut_region.region line_segment_list = [] for edge in graph.edge_list: edge_segment = graph.EdgeSegment(edge) for point in [edge_segment.point_a, edge_segment.point_b, edge_segment.Lerp(0.5)]: if region.ContainsPoint(point) and not region.ContainsPointOnBorder(point): line_segment_list.append(edge_segment) break # Now let all copied edges, if any, undergo a random symmetry of the chosen region, then add them to the graph. edge_count_before = len(graph.edge_list) if len(line_segment_list) > 0: i = random.randint(0, len(cut_region.symmetry_list) - 1) symmetry = cut_region.symmetry_list[i] for line_segment in line_segment_list: line_segment = symmetry * line_segment graph.Add(line_segment) edge_count_after = len(graph.edge_list) # Count how long we've gone without adding any new edges. added_edge_count = edge_count_after - edge_count_before if added_edge_count > 0: count = 0 print('Edge count: %d' % edge_count_after) else: count += 1 # For debugging purposes... if preview == 'graph_post_cut': DebugDraw(graph) # This can be used to generate a solution to the puzzle. generator_list = [] cloud = PointCloud() if self.PopulatePointCloudForPermutationGroup(cloud, graph): print('Generating permutations that generate the group...') for cut_region in self.cut_region_list: cut_region.permutation_list = [] for symmetry in cut_region.symmetry_list: permutation = [] for i, point in enumerate(cloud.point_list): if cut_region.region.ContainsPoint(point): point = symmetry * point j = cloud.FindPoint(point) if j is None: nearest_points, smallest_distance = cloud.FindNearestPoints(point) nearest_points = '[' + ','.join(['%d' % i for i in nearest_points]) + ']' raise Exception('Failed to generate permutation! Nearest points: %s; Smallest distance: %f' % (nearest_points, smallest_distance)) permutation.append(j) cut_region.permutation_list.append(permutation) if calc_solution: generator_list.append(Perm(permutation)) # If asked, try to find a stab-chain that can be used to solve the puzzle. stab_chain = None if calc_solution: # This is not necessarily the best base for solving the puzzle. base_array = [i for i in range(len(cloud.point_list))] # Try to generate a solution. print('Generating stab-chain...') stab_chain = StabChain() stab_chain.generate(generator_list, base_array) order = stab_chain.order() print('Group order = %d' % order) stab_chain.solve(self.SolveCallback) # Calculate a bounding rectangle for the graph, size it up a bit, then add it to the graph. # Also, generate a preview icon for the puzzle that can be used for selection purposes in the web-app. print('Adding border and drawing icon...') rect = AxisAlignedRectangle() rect.GrowFor(graph) rect.Scale(1.1) rect.ExpandToMatchAspectRatioOf(AxisAlignedRectangle(Vector(0.0, 0.0), Vector(1.0, 1.0))) preview_image = Image.new('RGBA', (128, 128), (255, 255, 255, 0)) preview_image_rect = AxisAlignedRectangle(Vector(0.0, 0.0), Vector(float(preview_image.width), float(preview_image.height))) preview_draw = ImageDraw.Draw(preview_image) for edge in graph.GenerateEdgeSegments(): edge = rect.Map(edge, preview_image_rect) edge = [ int(edge.point_a.x), preview_image.height - 1 - int(edge.point_a.y), int(edge.point_b.x), preview_image.height - 1 - int(edge.point_b.y) ] preview_draw.line(edge, fill=(0, 0, 0, 255), width=2) graph.Add(rect) # Before we can pull the empty cycles out of the graph, we need to merge all connected components into one. print('Coalescing all connected components...') while True: sub_graph_list, dsf_set_list = graph.GenerateConnectedComponents() if len(sub_graph_list) == 1: break smallest_distance = None line_segment = None for i in range(len(graph.vertex_list)): dsf_set_a = dsf_set_list[i] for j in range(i + 1, len(graph.vertex_list)): dsf_set_b = dsf_set_list[j] if dsf_set_a != dsf_set_b: distance = (graph.vertex_list[i] - graph.vertex_list[j]).Length() if smallest_distance is None or distance < smallest_distance: smallest_distance = distance line_segment = LineSegment(graph.vertex_list[i], graph.vertex_list[j]) graph.Add(line_segment) # For debugging purposes... if preview == 'graph_coalesced': DebugDraw(graph) # The desired meshes are now simply all of the empty cycles of the graph. print('Reading all empty cycles...') polygon_list = graph.GeneratePolygonCycles(epsilon=0.1) for polygon in polygon_list: polygon.Tessellate() # For debugging purposes... if preview == 'meshes': mesh_list = [polygon.mesh for polygon in polygon_list] DebugDraw(mesh_list) # Finally, write out the level file along with its accompanying mesh files. # Note that I think we can calculate UVs as a function of the object-space coordinates in the shader. print('Writing level files...') outline_thickness = rect.Width() / 100.0 preview_image.save(puzzle_folder + '/' + self.Name() + '_Icon.png') mesh_list = [] for i, cut_region in enumerate(self.cut_region_list): mesh_file = self.Name() + '_CaptureMesh%d.json' % i mesh = cut_region.region.GenerateMesh() outline_mesh = cut_region.region.GenerateLineMesh(outline_thickness) with open(puzzle_folder + '/' + mesh_file, 'w') as mesh_handle: mesh_handle.write(json.dumps({ 'mesh': mesh.Serialize(), 'outline_mesh': outline_mesh.Serialize() }, sort_keys=True, indent=4, separators=(',', ': '))) mesh_list.append({ 'file': mesh_file, 'type': 'capture_mesh', # Note that if the symmetry list has one entry, it's a reflection. # If it has 2 or more entries, then the first 2 are always rotations, the rest reflections. 'symmetry_list': [symmetry.Serialize() for symmetry in cut_region.symmetry_list], 'permutation_list': [permutation for permutation in cut_region.permutation_list] }) # A good draw-order is probably largest area to smallest area. polygon_list.sort(key=lambda polygon: polygon.Area(), reverse=True) for i, polygon in enumerate(polygon_list): mesh_file = self.Name() + '_PictureMesh%d.json' % i with open(puzzle_folder + '/' + mesh_file, 'w') as mesh_handle: mesh_handle.write(json.dumps({ 'mesh': polygon.mesh.Serialize() }, sort_keys=True, indent=4, separators=(',', ': '))) mesh_list.append({ 'file': mesh_file, 'type': 'picture_mesh', }) puzzle_file = self.Name() + '.json' with open(puzzle_folder + '/' + puzzle_file, 'w') as puzzle_handle: puzzle_handle.write(json.dumps({ 'mesh_list': mesh_list, 'window': rect.Serialize() }, sort_keys=True, indent=4, separators=(',', ': '))) if stab_chain is not None: stab_chain_file = puzzle_folder + '/' + self.Name() + '_StabChain.json' with open(stab_chain_file, 'w') as handle: json_data = stab_chain.to_json() handle.write(json_data)
class RenderNode(object): def __init__(self, person): super().__init__() self.person = person self.sub_node_map = {} self.label_string = person.name + '\n' + person.family_search_id self.label_box = AxisAlignedRectangle() self.bounding_box = None def calculate_size(self): size = 0 for node in self.all_nodes(): size += 1 return size def render_graph(self, draw, image, font, person_subset): image_rect = AxisAlignedRectangle( Vector(0.0, 0.0), Vector(float(image.width), float(image.height))) world_rect = self.bounding_box.Copy() world_rect.ExpandToMatchAspectRatioOf(image_rect) for node in self.all_nodes(): node.render_edges(draw, font, image_rect, world_rect) for node in self.all_nodes(): node.render_label_box(draw, font, image_rect, world_rect, person_subset) def render_edges(self, draw, font, image_rect, world_rect): point_a = world_rect.Map(self.label_box.Center(), image_rect) for key in self.sub_node_map: sub_node = self.sub_node_map[key] point_b = world_rect.Map(sub_node.label_box.Center(), image_rect) draw.line((point_a.x, point_a.y, point_b.x, point_b.y), fill=(50, 100, 100), width=1) point_c = (point_a + point_b) * 0.5 text_size = draw.textsize(text=key, font=font) draw.text( (point_c.x - text_size[0] / 2, point_c.y - text_size[1] / 2), text=key, font=font, fill=(0, 0, 0)) def render_label_box(self, draw, font, image_rect, world_rect, person_subset): point_a = world_rect.Map(self.label_box.min_point, image_rect) point_b = world_rect.Map(self.label_box.max_point, image_rect) color = (80, 32, 32) if self.person in person_subset else (32, 32, 32) if self.person.any_proxy_work_available: color = (32, 80, 23) draw.rectangle((point_a.x, point_a.y, point_b.x, point_b.y), fill=color) polygon = self.label_box.GeneratePolygon() for world_line in polygon.GenerateLineSegments(): image_line = world_rect.Map(world_line, image_rect) draw.line((image_line.point_a.x, image_line.point_a.y, image_line.point_b.x, image_line.point_b.y), fill=(255, 0, 0), width=1) point = world_rect.Map(self.label_box.Center(), image_rect) text_size = draw.textsize(text=self.label_string, font=font) draw.text((point.x - text_size[0] / 2, point.y - text_size[1] / 2), text=self.label_string, font=font, fill=(200, 200, 200)) def transform_graph_layout(self, transform): for node in self.all_nodes(): node.label_box = transform.Transform(node.label_box) node.bounding_box = transform.Transform(node.bounding_box) def calculate_graph_layout(self, draw, font): text_size = draw.textsize(text=self.label_string, font=font) text_width = text_size[0] text_height = text_size[1] total_width = 0.0 margin = 2.0 for key in self.sub_node_map: sub_node = self.sub_node_map[key] sub_node.calculate_graph_layout(draw, font) sub_node.calculate_bounding_box() total_width += sub_node.bounding_box.Width() + 2.0 * margin location = Vector(-total_width / 2.0 + margin, -2.0 * text_height) for key in self.sub_node_map: sub_node = self.sub_node_map[key] transform = AffineTransform() upper_left_corner = Vector(sub_node.bounding_box.min_point.x, sub_node.bounding_box.max_point.y) transform.Translation(location - upper_left_corner) sub_node.transform_graph_layout(transform) location.x += sub_node.bounding_box.Width() + 2.0 * margin self.label_box.min_point = Vector(-text_width / 2.0 - 3.0, -text_height / 2.0 - 3.0) self.label_box.max_point = Vector(text_width / 2.0 + 3.0, text_height / 2.0 + 3.0) def calculate_bounding_box(self): self.bounding_box = self.label_box.Copy() for node in self.all_nodes(): self.bounding_box.GrowFor(node.label_box) def all_nodes(self): queue = [self] while len(queue) > 0: node = queue.pop() yield node for key in node.sub_node_map: sub_node = node.sub_node_map[key] queue.append(sub_node) def prune_tree(self, person_set): del_key_list = [] for key in self.sub_node_map: sub_node = self.sub_node_map[key] if not sub_node.any_person_found_in(person_set): del_key_list.append(key) for key in del_key_list: del self.sub_node_map[key] for key in self.sub_node_map: sub_node = self.sub_node_map[key] sub_node.prune_tree(person_set) def any_person_found_in(self, person_set): for node in self.all_nodes(): if node.person in person_set: return True # TODO: Write an optimizer for the tree. E.g., your father's spouse's son is just your brother. # Note that some people can't be removed from the tree if they fall within a given set. # I.e., the set of people we want to see in the tree. def construct_using_path(self, path, i=0): if i < len(path): component = path[i] if component[0] == 'mother': next_person = self.person.mother key = 'Mother' elif component[0] == 'father': next_person = self.person.father key = 'Father' elif component[0] == 'child': next_person = self.person.child_list[component[1]] key = 'Child %d' % (component[1] + 1) elif component[0] == 'spouse': next_person = self.person.spouse_list[component[1]] key = 'Spouse %d' % (component[1] + 1) else: raise Exception('Unknown component: %s' % component[0]) if key not in self.sub_node_map: self.sub_node_map[key] = RenderNode(person=next_person) self.sub_node_map[key].construct_using_path(path, i + 1)