def SplitLineSegment(self, given_line_segment, assume_convex=False): # Chop up the given line segment against this polygon. line_segment_list = [] queue = [given_line_segment.Copy()] while len(queue) > 0: line_segment = queue.pop() for edge_segment in self.GenerateLineSegments(): point = line_segment.IntersectWith(edge_segment) if point is not None and not line_segment.IsEndPoint(point): line_segment_a = LineSegment(point_a=line_segment.point_a, point_b=point) line_segment_b = LineSegment(point_a=point, point_b=line_segment.point_b) queue.append(line_segment_a) queue.append(line_segment_b) break else: line_segment_list.append(line_segment) # Now bucket-sort the chopped-up parts into the correct list. in_list = [] out_list = [] for line_segment in line_segment_list: mid_point = line_segment.Lerp(0.5) if self.ContainsPoint(mid_point, assume_convex=assume_convex): in_list.append(line_segment) else: out_list.append(line_segment) return in_list, out_list
def Reduce(self, epsilon=1e-7): # Here we're removing all vertices of degree 2 where the incident # edges are approximately co-linear. keep_going = True while keep_going: keep_going = False for i in range(len(self.vertex_list)): adjacency_list = self.FindAllAdjacencies(i, ignore_direction=True, vertices=False) if len(adjacency_list) == 2: edge_a = adjacency_list[0] edge_b = adjacency_list[1] point_a = self.vertex_list[ edge_a[0]] if edge_a[0] != i else self.vertex_list[ edge_a[1]] point_b = self.vertex_list[ edge_b[0]] if edge_b[0] != i else self.vertex_list[ edge_b[1]] line_seg = LineSegment(point_a=point_a, point_b=point_b) distance = line_seg.Distance(self.vertex_list[i]) if distance < epsilon: self.RemoveVertex(i) self.Add(line_seg, disposition={}, epsilon=epsilon) keep_going = True break
def Render(self, point, arrow_head_length=0.3): from OpenGL.GL import glBegin, glEnd, glVertex2f, GL_LINES from math2d_affine_transform import AffineTransform from math2d_line_segment import LineSegment transform = AffineTransform() transform.linear_transform.x_axis = self.Normalized() transform.linear_transform.y_axis = transform.linear_transform.x_axis.Rotated( math.pi / 2.0) transform.translation = point line_segment_list = [] head = Vector(self.Length(), 0.0) line_segment_list.append(LineSegment(Vector(0.0, 0.0), head)) if arrow_head_length > 0.0: line_segment_list.append( LineSegment( head, head + Vector(radius=arrow_head_length, angle=(7.0 / 8.0) * math.pi))) line_segment_list.append( LineSegment( head, head + Vector(radius=arrow_head_length, angle=-(7.0 / 8.0) * math.pi))) glBegin(GL_LINES) try: for line_segment in line_segment_list: line_segment = transform * line_segment glVertex2f(line_segment.point_a.x, line_segment.point_a.y) glVertex2f(line_segment.point_b.x, line_segment.point_b.y) finally: glEnd()
def advance_positions(self, lerp_value, eps=1e-2): line_segment = LineSegment(self.position, self.target_position) if line_segment.Length() < eps: self.position = self.target_position else: self.position = line_segment.Lerp(lerp_value) for child in self.child_list: child.advance_positions(lerp_value)
def Length(self): from math2d_line_segment import LineSegment if len(self.point_list) < 2: return 0.0 length = 0.0 for i in range(len(self.point_list) - 1): line_segment = LineSegment(self.point_list[i], self.point_list[i + 1]) length += line_segment.Length() return length
def Interpolate(self, value): from math2d_line_segment import LineSegment point_list = [point for point in self.point_list] while len(point_list) > 1: new_point_list = [] for i in range(len(point_list) - 1): line_segment = LineSegment(point_list[i], point_list[i + 1]) new_point_list.append(line_segment.Lerp(value)) point_list = new_point_list return point_list[0]
def Map(self, thing, rectangle): if isinstance(thing, Vector): u, v = self.CalcUVs(thing) return rectangle.ApplyUVs(u, v) elif isinstance(thing, LineSegment): return LineSegment(self.Map(thing.point_a, rectangle), self.Map(thing.point_b, rectangle))
def animation_tick(self): if isinstance(self.root_node, MathTreeNode): if not self.root_node.is_settled(): self.root_node.advance_positions(0.3) self.update() elif self.auto_simplify: self.do_simplify_step() if self.proj_rect is not None: if ((self.anim_proj_rect.min_point - self.proj_rect.min_point).Length() > 0.0 or (self.anim_proj_rect.max_point - self.proj_rect.max_point).Length() > 0.0): self.anim_proj_rect.min_point = LineSegment( self.anim_proj_rect.min_point, self.proj_rect.min_point).Lerp(0.1) self.anim_proj_rect.max_point = LineSegment( self.anim_proj_rect.max_point, self.proj_rect.max_point).Lerp(0.1) self.update()
def GenerateEdgeSegments(self): edge_set = set() for vertex in self.vertex_list: key_a = hex(id(vertex)) for adjacent_vertex in vertex.adjacency_list: key_b = hex(id(adjacent_vertex)) key = key_a + key_b if key_a < key_b else key_b + key_a if key not in edge_set: edge_set.add(key) yield LineSegment(point_a=vertex.point, point_b=adjacent_vertex.point)
def CastAgainst(self, other, cast_distance=1000.0): if isinstance(other, Circle): pass elif isinstance(other, LineSegment): ray_segment = LineSegment(point_a=self.point, point_b=self.EvalParam(cast_distance)) hit_point = ray_segment.IntersectWith(other) if hit_point is not None: return self.CalcParam(hit_point) elif isinstance(other, Ray): pass elif isinstance(other, Polygon): pass elif type(other) is list: smallest_hit = None for item in other: hit = self.CastAgainst(item) if hit is not None and hit >= 0.0 and (smallest_hit is None or hit < smallest_hit): smallest_hit = hit return smallest_hit
def Interpolate(self, value, length=None): from math2d_line_segment import LineSegment if length is None: length = self.Length() distance = length * value if distance < 0.0 or distance > length: raise Exception('Invalid parameter value.') i = 0 point = None while distance >= 0.0: point = self.point_list[i] line_segment = LineSegment(self.point_list[i], self.point_list[i + 1]) segment_length = line_segment.Lenght() if segment_length < distance: distance -= segment_length i += 1 else: lerp_value = segment_length / distance point = line_segment.Lerp(lerp_value) break return point
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 GenerateEdgeSegments(self): for i in range(3): j = (i + 1) % 3 yield LineSegment(self.Vertex(i), self.Vertex(j))
def Add(self, other, disposition={}, epsilon=1e-7, depth=0): from math2d_region import Region, SubRegion from math2d_polygon import Polygon from math2d_aa_rect import AxisAlignedRectangle if type(other) is list: for item in other: self.Add(item, disposition, epsilon, depth + 1) elif isinstance(other, PlanarGraph): for edge in other.edge_list: line_segment = other.EdgeSegment(edge) self.Add(line_segment, { **disposition, 'edge_label': edge[2] }, epsilon, depth + 1) elif isinstance(other, AxisAlignedRectangle): polygon = other.GeneratePolygon() self.Add(polygon, disposition, epsilon, depth + 1) elif isinstance(other, Region): for sub_region in other.sub_region_list: self.Add(sub_region, disposition, epsilon, depth + 1) elif isinstance(other, SubRegion): self.Add(other.polygon, disposition) for hole in other.hole_list: self.Add(hole, { **disposition, 'flip_edge_direction': True }, epsilon, depth + 1) elif isinstance(other, Polygon): for line_segment in other.GenerateLineSegments(): self.Add(line_segment, disposition, epsilon, depth + 1) elif isinstance(other, LineSegment): for vertex in self.vertex_list: if other.ContainsPoint( vertex, epsilon) and not other.IsEndPoint(vertex, epsilon): self.Add(LineSegment(other.point_a, vertex), disposition, epsilon, depth + 1) self.Add(LineSegment(vertex, other.point_b), disposition, epsilon, depth + 1) return for i, edge in enumerate(self.edge_list): edge_segment = self.EdgeSegment(edge) if not (other.IsEndPoint(edge_segment.point_a, epsilon) or other.IsEndPoint(edge_segment.point_b, epsilon)): point = edge_segment.IntersectWith(other) if point is not None: if not edge_segment.IsEndPoint(point): del self.edge_list[i] self.Add(LineSegment(edge_segment.point_a, point), {'edge_label': edge[2]}, epsilon, depth + 1) self.Add(LineSegment(point, edge_segment.point_b), {'edge_label': edge[2]}, epsilon, depth + 1) if not other.IsEndPoint(point): self.Add(LineSegment(other.point_a, point), disposition, epsilon, depth + 1) self.Add(LineSegment(point, other.point_b), disposition, epsilon, depth + 1) return i = self.FindVertex(other.point_a, add_if_not_found=True, epsilon=epsilon) j = self.FindVertex(other.point_b, add_if_not_found=True, epsilon=epsilon) label = disposition.get('edge_label', PlanarGraphEdgeLabel.NONE) if disposition.get('flip_edge_direction', False): new_edge = (j, i, label) else: new_edge = (i, j, label) if new_edge[0] == new_edge[1]: raise Exception('Tried to add degenerate line-segment.') k = self.FindEdge(new_edge) if k is not None: if disposition.get('replace_edges', False): self.edge_list[k] = new_edge elif disposition.get('duplicate_edges', False): self.edge_list.append(new_edge) else: self.edge_list.append(new_edge)
def GenerateSymmetries(self): reflection_list = [] center_reflection_list = [] center = self.AveragePoint() for i in range(len(self.point_list)): for j in range(i + 1, len(self.point_list)): line_segment = LineSegment(self.point_list[i], self.point_list[j]) mid_point = line_segment.Lerp(0.5) normal = line_segment.Direction().Normalized().RotatedCCW90() reflection = AffineTransform() reflection.Reflection(mid_point, normal) center_reflected = reflection.Transform(center) if center_reflected.IsPoint(center): center_reflection_list.append({ 'reflection': reflection, 'normal': normal }) is_symmetry, total_error = self.IsSymmetry(reflection) if is_symmetry: new_entry = { 'reflection': reflection, 'total_error': total_error, 'center': mid_point, 'normal': normal } for k, entry in enumerate(reflection_list): if entry['reflection'].IsTransform(reflection): if entry['total_error'] > total_error: reflection_list[k] = new_entry break else: reflection_list.append(new_entry) # Rotations are just double-reflections. We return here a CCW rotational symmetry that generates # the sub-group of rotational symmetries of the overall group of symmetries of the cloud. We also # return its inverse for convenience. Of course, not all point clouds have any rotational symmetry. epsilon = 1e-7 def SortKey(entry): if entry['normal'].y <= -epsilon: entry['normal'] = -entry['normal'] angle = Vector(1.0, 0.0).SignedAngleBetween(entry['normal']) if angle < 0.0: angle += 2.0 * math.pi return angle ccw_rotation = None cw_rotation = None if len(reflection_list) >= 2: reflection_list.sort(key=SortKey) # Any 2 consecutive axes should be as close in angle between each other as possible. reflection_a = reflection_list[0]['reflection'] reflection_b = reflection_list[1]['reflection'] ccw_rotation = reflection_a * reflection_b cw_rotation = reflection_b * reflection_a # The following are just sanity checks. is_symmetry, total_error = self.IsSymmetry(ccw_rotation) if not is_symmetry: raise Exception('Failed to generate CCW rotational symmetry.') is_symmetry, total_error = self.IsSymmetry(cw_rotation) if not is_symmetry: raise Exception('Failed to generate CW rotational symmetry.') elif len(reflection_list) == 1: # If we found exactly one reflection, then I believe the cloud has no rotational symmetry. # Note that the identity transform is not considered a symmetry. pass else: # If we found no reflective symmetry, the cloud may still have rotational symmetry. # Furthermore, if it does, it must rotate about the average point. I think there is a way # to prove this using strong induction. Note that the statement holds for all point-clouds # made from vertices taken from regular polygons. Now for any given point-cloud with any # given rotational symmetry, consider 1 point of that cloud. Removing that point along # with the minimum number of other points necessary to keep the given rotational symmetry, # we must remove points making up a regular polygon's vertices. By inductive hypothesis, # the remaining cloud has its average point at the center of the rotational symmetry. Now # see that adding the removed points back does not change the average point of the cloud. # Is it true that every line of symmetry of the cloud contains the average point? I believe # the answer is yes. Take any point-cloud with a reflective symmetry and consider all but # 2 of its points that reflect into one another along that symmetry. If the cloud were just # these 2 points, then the average point is on the line of symmetry. Now notice that as you # add back points by pairs, each pair reflecting into one another, the average point of the # cloud remains on the line of symmetry. center_reflection_list.sort(key=SortKey) found = False for i in range(len(center_reflection_list)): for j in range(i + 1, len(center_reflection_list)): ccw_rotation = center_reflection_list[i][ 'reflection'] * center_reflection_list[j]['reflection'] is_symmetry, total_error = self.IsSymmetry(ccw_rotation) if is_symmetry: # By stopping at the first one we find, we should be minimizing the angle of rotation. found = True break if found: break if found: cw_rotation = ccw_rotation.Inverted() else: ccw_rotation = None return reflection_list, ccw_rotation, cw_rotation
def GenerateLineMesh(self, thickness=0.5, epsilon=1e-7): half_thickness = thickness / 2.0 from math2d_tri_mesh import TriangleMesh, Triangle from math2d_polygon import Polygon from math2d_affine_transform import AffineTransform mesh = TriangleMesh() def SortKey(vector): angle = Vector(1.0, 0.0).SignedAngleBetween(vector) if angle < 0.0: angle += 2.0 * math.pi return angle polygon_list = [] for i in range(len(self.vertex_list)): center = self.vertex_list[i] vector_list = [] adj_list = self.FindAllAdjacencies(i, ignore_direction=True, vertices=True) for j in adj_list: vector_list.append(self.vertex_list[j] - center) vector_list.sort(key=SortKey) polygon = Polygon() for j in range(len(vector_list)): vector_a = vector_list[j] vector_b = vector_list[(j + 1) % len(vector_list)] angle = vector_a.SignedAngleBetween(vector_b) if angle < 0.0: angle += 2.0 * math.pi if math.fabs(angle - math.pi) < epsilon or angle < math.pi: affine_transform = AffineTransform() affine_transform.translation = center affine_transform.linear_transform.x_axis = vector_a.Normalized( ) affine_transform.linear_transform.y_axis = affine_transform.linear_transform.x_axis.RotatedCCW90( ) if math.fabs(angle - math.pi) < epsilon: length = 0.0 else: length = half_thickness / math.tan(angle / 2.0) point = affine_transform * Vector(length, half_thickness) polygon.vertex_list.append(point) else: # Here we might create a rounded joint or something fancy, but this is good for now. polygon.vertex_list.append(vector_a.Normalized( ).RotatedCCW90().Scaled(half_thickness) + center) polygon.vertex_list.append(vector_b.Normalized( ).RotatedCW90().Scaled(half_thickness) + center) polygon.Tessellate() mesh.AddMesh(polygon.mesh) polygon_list.append(polygon) for edge in self.edge_list: center_a = self.vertex_list[edge[0]] center_b = self.vertex_list[edge[1]] line_segment = LineSegment(center_a, center_b) def FindEdgeSegment(polygon): for edge_segment in polygon.GenerateLineSegments(): point = edge_segment.IntersectWith(line_segment) if point is not None: return edge_segment else: raise Exception('Failed to find line quad end.') polygon_a = polygon_list[edge[0]] polygon_b = polygon_list[edge[1]] edge_segment_a = FindEdgeSegment(polygon_a) edge_segment_b = FindEdgeSegment(polygon_b) triangle_a = Triangle(edge_segment_a.point_a, edge_segment_b.point_b, edge_segment_b.point_a) triangle_b = Triangle(edge_segment_a.point_a, edge_segment_b.point_a, edge_segment_a.point_b) area_a = triangle_a.Area() area_b = triangle_b.Area() if area_a < 0.0 or area_b < 0.0: raise Exception('Miscalculated line quad triangles.') mesh.AddTriangle(triangle_a) mesh.AddTriangle(triangle_b) return mesh
def EdgeSegment(self, edge): return LineSegment(self.vertex_list[edge[0]], self.vertex_list[edge[1]])
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)
def Center(self): return LineSegment(self.min_point, self.max_point).Lerp(0.5)
def GenerateLineSegments(self): for i in range(len(self.vertex_list)): j = (i + 1) % len(self.vertex_list) line_segment = LineSegment(self.vertex_list[i], self.vertex_list[j]) yield line_segment
def GenerateLineSegments(self): for i in range(len(self.vertex_list) - 1): yield LineSegment(point_a=self.vertex_list[i], point_b=self.vertex_list[i + 1])
def _Tessellate(self, given_mesh): self.RemoveRedundantVertices() if len(self.vertex_list) < 3: return elif len(self.vertex_list) == 3: triangle = Triangle(self.vertex_list[0], self.vertex_list[1], self.vertex_list[2]) given_mesh.AddTriangle(triangle) else: candidate_list = [] for i in range(len(self.vertex_list)): for j in range(i + 1, len(self.vertex_list)): if (i + 1) % len(self.vertex_list) != j and (i - 1) % len( self.vertex_list) != j: candidate_list.append((i, j)) # The idea here is that we might get better tessellations if we try the candidates in this order. candidate_list.sort(key=lambda pair: (self.vertex_list[pair[ 1]] - self.vertex_list[pair[0]]).Length(), reverse=True) for pair in candidate_list: i = pair[0] j = pair[1] line_segment = LineSegment(self.vertex_list[i], self.vertex_list[j]) try: for k in range(len(self.vertex_list)): if k != i and k != j: if line_segment.ContainsPoint(self.vertex_list[k]): raise Exception() if k != i and (k + 1) % len(self.vertex_list) != i: if k != j and (k + 1) % len(self.vertex_list) != j: edge_segment = LineSegment( self.vertex_list[k], self.vertex_list[(k + 1) % len(self.vertex_list)]) point = edge_segment.IntersectWith( line_segment) if point is not None: raise Exception() polygon_a = Polygon() k = 0 while True: r = (j + k) % len(self.vertex_list) polygon_a.vertex_list.append(self.vertex_list[r]) if r == i: break k += 1 if polygon_a.IsWoundCW(): raise Exception() polygon_b = Polygon() k = 0 while True: r = (i + k) % len(self.vertex_list) polygon_b.vertex_list.append(self.vertex_list[r]) if r == j: break k += 1 if polygon_b.IsWoundCW(): raise Exception() except: pass else: polygon_a._Tessellate(given_mesh) polygon_b._Tessellate(given_mesh) break else: raise Exception('Failed to tessellate polygon!')