Esempio n. 1
0
    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
Esempio n. 3
0
 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()
Esempio n. 4
0
 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)
Esempio n. 5
0
 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
Esempio n. 6
0
 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]
Esempio n. 7
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))
Esempio n. 8
0
 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()
Esempio n. 9
0
 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)
Esempio n. 10
0
 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
Esempio n. 11
0
 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
Esempio n. 12
0
 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))
Esempio n. 13
0
 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)
Esempio n. 15
0
    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]])
Esempio n. 18
0
    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)
Esempio n. 19
0
 def Center(self):
     return LineSegment(self.min_point, self.max_point).Lerp(0.5)
Esempio n. 20
0
 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
Esempio n. 21
0
 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])
Esempio n. 22
0
 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!')