Example #1
0
    def _parallel(*segments):
        """
        two segments are parallel if, translating both to the origin we have two COLLINEAR segments
        """
        if not all(isinstance(s, Line) for s in segments):
            raise TypeError("Line._parallel requires all Line objects")

        unique_segments = list(set(segments))

        if len(unique_segments) == 0:
            return False
        elif len(unique_segments) == 1:
            return True
        else:
            # take the first segment and translate it to the origin
            first_translated_seg = Line(
                [Point3(0, 0, 0), (segments[0].end - segments[0].start)])

            # the given segments are parallel if they are all parallel to the first
            for s in segments[1:]:
                translated_seg = Line([Point3(0, 0, 0), (s.end - s.start)])
                if not first_translated_seg.is_collinear_with(translated_seg):
                    return False

            return True
Example #2
0
    def __init__(self,
                 p=Point3(0, 0, 0),
                 p1=None,
                 p2=None,
                 normal=Vector3(0, 0, 1)):
        """
        a plane can be defined either by three non-_collinear points or by a point and a vetor denotin the normal
        to the plane. The normal vector has priority over the points. I.e., if both the normal vector and points 1 and 2
        are given, p1 and p2 are ignored
        :param p: the first point
        :param p1: the second point (mutually exclusive with normal)
        :param p2: the third point (mutually exclusive with normal)
        :param normal: the normal vector (mutually exclusive with point 1 and 2)
        """
        self._position = p

        if normal and isinstance(normal, Vector3):
            self._normal = normal / normal.magnitude(
            )  # normalize the vector (i.e., magnitude = 1)
        elif p1 and isinstance(p1, Point3) and p2 and isinstance(p2, Point3):
            if Point3.are_collinear(p, p1, p2):
                # if the three points are _collinear raise an error
                raise ValueError(
                    'To define a plane three non-_collinear points must be given'
                )

            # compute the normal to the triangle p-p1-p2
            cross_prod = (p2 - p).cross(p2 - p)
            self._normal = cross_prod / cross_prod.magnitude  # normalize the vector (i.e., magnitude = 1)
Example #3
0
    def __init__(self, parent=None):
        self.parent = parent
        QtOpenGL.QGLWidget.__init__(self, parent)

        self.shader_program = None  # QtOpenGL.QGLShaderProgram()

        self.camera = Camera(
        )  # initialize a camera object (initially located at the origin and facing (0, 0, -1)

        self.xy_grid = MyGrid()

        self.renderables3d = set(
        )  # this contains all the geometries on canvas

        self.selection_box = None  # this contains at most 1 selection box

        self.last_mouse_position = QPoint(
            0, 0)  # last position of the mouse in viewport-coordinates

        self.bounding_box = Box3D(the_min=Point3(-20, -20, -20),
                                  the_max=Point3(20, 20, 20))
        # the bounding_box of all the geometries in the canvas.
        # BUT is never smaller than the values set here! Because it is used to set the viewport in VIEW_2D mode

        self.mode = Canvas3d.MODE_EXPLORE  # by default in "scene exploration" mode
        self.draw_what = None  # when in MODE_DRAW, this contains an indication of what is being drawn, else None
        self.currently_drawn_geom = None  # this maintains a reference to a geometry being drawn, until the drawing is complete

        self.selected_geoms = set(
        )  # will contain geometries in the canvas selected via the select tool

        self.canvas_cube_proportion = None
        self.setMouseTracking(True)
Example #4
0
    def set_boundary(self):
        v1 = Point3(self.corner1)
        v2 = Point3(self.corner2.x(), self.corner1.y(), 0)
        v3 = Point3(self.corner2)
        v4 = Point3(self.corner1.x(), self.corner2.y(), 0)

        self.boundary = LinearRing(vertices=[v1, v2, v3, v4, v1],
                                   color=Color(0, 0, 0))
Example #5
0
    def __init__(self, vertices=None, vector=None, color=None):
        """
        creates a Line. if start and end are not given makes a degenerate segment (a point at the origin).
        if vector is given and is a Vector3 makes a segment going from the origin to the coordinates of the vector
        """

        if vector and isinstance(vector, Vector3):
            vertices = [Point3(0, 0, 0), Point3(vector)]

        if vertices and len(vertices) > 2:
            vertices = vertices[:2]
        LineString.__init__(self, vertices=vertices, color=color)
Example #6
0
    def update_bounding_box(self):
        if len(self) > 0:
            vertices = self.vertices
            the_min = Point3(vertices[0])
            the_max = Point3(vertices[0])

            for v in vertices:
                for i, coord in enumerate(v.tolist()):
                    the_min[i] = min(the_min[i], coord)
                    the_max[i] = max(the_max[i], coord)

            self.bounding_box.set_min(the_min)
            self.bounding_box.set_max(the_max)
Example #7
0
    def drawingEventAddVertex(self, event):
        """if no geometry is being drawn, create one.
        add a vertex to the drawn geometry"""

        # only if in draw mode
        if self.mode == Canvas3d.MODE_DRAW:
            x, y, z = self.camera.canvas2world(event.x(), event.y())
            new_point = Point3(x, y, z)
            if self.draw_what == Canvas3d.DRAW_POINTS:
                self.currently_drawn_geom = new_point
            elif self.draw_what == Canvas3d.DRAW_SEGMENTS:
                if self.currently_drawn_geom is None:
                    self.currently_drawn_geom = Line()
                self.currently_drawn_geom.add_vertex(new_point)
            elif self.draw_what == Canvas3d.DRAW_POLYGONS:
                if self.currently_drawn_geom is None:
                    self.currently_drawn_geom = Polygon()
                self.currently_drawn_geom.add_vertex(new_point)
            elif self.draw_what == Canvas3d.DRAW_POLYLINES:
                if self.currently_drawn_geom is None:
                    self.currently_drawn_geom = LineString()
                self.currently_drawn_geom.add_vertex(new_point)

            if isinstance(self.currently_drawn_geom, Geometry):
                self.add_geometry(self.currently_drawn_geom)
Example #8
0
    def point_position(self, a_point, perspective_point=Point3(0, 0, 1)):
        """
        Check the position of a_point w.r.t. the segment on the plane formed by the segment and the point,
        as viewed from the perspective_point
        :param a_point: a Point object
        :param perspective_point: observation point (the default value should be used ONLY when working in 2D)
        :return: False, if the perspective point is COPLANAR with self and the a_point;
                 otherwise, either RIGHT (-1), LEFT (1), BEFORE (-2), BETWEEN (0), or AFTER (2)
        """

        if Point3.are_coplanar(self.start, self.end, a_point,
                               perspective_point):
            return False

        return Point3.orientation(self.start, self.end, a_point,
                                  perspective_point)
Example #9
0
def points_centroid(geoms, canvas=None):
    """only consider Points in the canvas. Compute and display
    the centroid obtained by the points."""

    centroid_x = 0
    centroid_y = 0
    centroid_z = 0
    points_num = 0

    for g in geoms:
        if isinstance(g, Point3):
            points_num += 1
            centroid_x += g.x()
            centroid_y += g.y()
            centroid_z += g.z()

    centroid_x /= points_num  # equivalent to centroid_x = centroid_x / points_num
    centroid_y /= points_num
    centroid_z /= points_num

    centroid = Point3(centroid_x, centroid_y, centroid_z)

    canvas.add_geometry(centroid)

    return "The centroid is: " + str(centroid)
Example #10
0
    def move_corner2(self, delta=None):
        if delta is None:
            delta = Point3(0, 0, 0)

        assert isinstance(delta, Point3)
        self.corner2 += delta

        self.set_boundary()
Example #11
0
    def get_classical_bbox(self):
        """
        this class is defined in a way that corner1 and corner2 may represent
        one of the following cases
        c2-------|--------c2
        |        |        |
        |    2   |    1   |
        |        |        |
        |--------c1-------|
        |        |        |
        |   3    |    4   |
        |        |        |
        c2-------|--------c2

        computes a "classical" bounding box: bottom-left and top-right corners
        :return: bottom-left (bl) and top-right(tr) corners
        """
        bl, tr = self.corner1, self.corner2
        # if c1 is on the left of c2
        if self.corner1.x() < self.corner2.x():
            # if c1 is below c2
            if self.corner1.y() < self.corner2.y():
                # we are in the case #1
                bl, tr = self.corner1, self.corner2
            # if c1 is above c2
            elif self.corner1.y() > self.corner2.y():
                # we are in the case #4
                bl = Point3(self.corner1.x(), self.corner2.y(), 0)
                tr = Point3(self.corner2.x(), self.corner1.y(), 0)
        # if c1 is on the right of c2
        elif self.corner1.x() > self.corner2.x():
            # if c1 is below c2
            if self.corner1.y() < self.corner2.y():
                # we are in the case #2
                bl = Point3(self.corner2.x(), self.corner1.y(), 0)
                tr = Point3(self.corner1.x(), self.corner2.y(), 0)
            # if c1 is above c2
            elif self.corner1.y() > self.corner2.y():
                # we are in the case #3
                bl, tr = self.corner2, self.corner1
        return bl, tr
Example #12
0
    def __init__(self, c1=None, c2=None):

        if c1 is None:
            c1 = Point3(0, 0, 0)

        if c2 is None:
            # corner2 = corner1
            #  not a good choice because we would only have only
            # 1 Point referred by two different names

            # this is the way to go
            c2 = Point3(c1)

        assert isinstance(c1, Point3)
        assert isinstance(c2, Point3)

        self.corner1 = c1
        self.corner2 = c2
        self.boundary = LinearRing

        self.set_boundary()
Example #13
0
    def contains_point(self, a_point):
        boundary = list(self.boundary)
        boundary = boundary[1:]
        is_in = False

        ray = Line([
            Point3(a_point.x(), a_point.y()),
            Point3(a_point.x(), a_point.y())
        ])

        for i, v_i in enumerate(boundary):
            curr_edge = Line([boundary[i - 1], boundary[i]])

            x_max = max(curr_edge.start.x(), curr_edge.end.x())
            if x_max > a_point.x():
                ray.end.set_x(x_max + 1)

            if ray.intersects(curr_edge):
                is_in = not is_in

        return is_in
Example #14
0
    def drawingEventMoveLastVertex(self, event):
        """move the last drawn vertex of the currently drawn geometry"""

        # only if in draw mode
        if self.mode == Canvas3d.MODE_DRAW:
            if self.currently_drawn_geom is not None:
                x, y, z = self.camera.canvas2world(event.x(), event.y())
                if self.draw_what == Canvas3d.DRAW_POINTS:
                    self.currently_drawn_geom.set_coords(x, y, z)
                elif self.draw_what in (Canvas3d.DRAW_SEGMENTS,
                                        Canvas3d.DRAW_POLYGONS,
                                        Canvas3d.DRAW_POLYLINES):
                    self.currently_drawn_geom.update_vertex(Point3(x, y, z))
Example #15
0
    def _collinear(*segments):
        if not all(isinstance(s, Line) for s in segments):
            raise TypeError("Line._collinear requires all Line objects")

        unique_segments = list(set(segments))

        if len(unique_segments) == 0:
            return False
        elif len(unique_segments) == 1:
            return True
        else:
            points = []
            for s in segments:
                points.append(s.start)
                points.append(s.end)

            return Point3.are_collinear(*points)
Example #16
0
    def mouseMoveEvent(self, event):

        if self.camera.view_mode == Camera.VIEW_2D:
            x, y, z = self.camera.canvas2world(event.x(), event.y())
            self.window().update_status_bar("(x, y): ({:.3f}, {:.3f})".format(
                x, y))

        # if moving while pressing the left button
        if event.buttons() and event.buttons() == QtCore.Qt.LeftButton:
            # if in draw mode: continue drawing
            if self.mode == Canvas3d.MODE_DRAW:
                self.drawingEventMoveLastVertex(event)

            # if we are in "selection" or "erase" mode
            elif self.mode == Canvas3d.MODE_SELECT or self.mode == Canvas3d.MODE_ERASE:
                # selection events are only accepted in 2D View
                if self.camera.view_mode == Camera.VIEW_2D:
                    event_x, event_y, z = self.camera.canvas2world(
                        event.x(), event.y())
                    x, y, z = self.camera.canvas2world(
                        self.last_mouse_position.x(),
                        self.last_mouse_position.y())
                    delta_x, delta_y = event_x - x, event_y - y
                    self.selection_box.move_corner2(Point3(
                        delta_x, delta_y, 0))

            # if in exploration mode
            elif self.mode == Canvas3d.MODE_EXPLORE:
                # if no button is held down: pan
                if QtGui.QApplication.keyboardModifiers(
                ) == QtCore.Qt.NoModifier:
                    self.panEvent(event)

                # if in 3D View
                if self.camera.view_mode == Camera.VIEW_3D:
                    # if the ctrl (cmd on mac) button is held down: rotate
                    if QtGui.QApplication.keyboardModifiers(
                    ) == QtCore.Qt.ControlModifier:
                        self.rotateEvent(event)

        # update the rendered scene
        self.updateGL()

        self.last_mouse_position = event.pos()
        event.accept()
Example #17
0
    def mousePressEvent(self, event):
        # when we press a mouse button on the canvas the canvas get the keyboard focus
        self.setFocus()

        # if in drawing mode
        if self.mode == Canvas3d.MODE_DRAW:
            # drawing events are only accepted in 2D View
            if self.camera.view_mode == Camera.VIEW_2D:
                self.drawingEventAddVertex(event=event)

        # if we are in "selection" or "erase" mode
        elif self.mode == Canvas3d.MODE_SELECT or self.mode == Canvas3d.MODE_ERASE:
            # selection events are only accepted in 2D View
            if self.camera.view_mode == Camera.VIEW_2D:
                event_x, event_y, z = self.camera.canvas2world(
                    event.x(), event.y())

                self.selection_box = SelectionBox(Point3(event_x, event_y, 0))

        # update the rendered scene
        self.updateGL()

        self.last_mouse_position = event.pos()
        event.accept()
Example #18
0
    def make_from_solid(self, solid):
        """
        a solid Must have a vertex array and an element array.
        The DCEL is generated straightworwardly from those.
        REMEMBER: by design the element array defines triplets of vertices (triangles)
        :param solid: a Solid object
        :return: fills the DCEL
        """

        from app.geoms.point import Point3

        self.reset()  # first reset the DCEL

        vertices = solid.renderable_vertex_array['coords'].tolist()
        elements = solid.renderable_element_array.tolist()

        # prepare a new list of triplets with each triplet denoting a triangular face
        faces_elements = [tuple(elements[i:i+3]) for i in range(0, len(elements), 3)]

        # In the following structure we keep a reference to incomplete hedges.
        # Namely, these hedges will only have a reference to the twin hedge and to the target vertex
        # An edge will be removed from here when all its references are set.
        # Namely, also the references next_edge, prev_edge, and adjacent_face
        incomplete_edges_by_source_and_target = dict()  # key, value = (source element id, target element id), edge

        # In here we store the Vertex objects we created,
        # in order not to create several Vertex objects for the same point
        created_vertices_by_element = dict()  # key, value = element, Vertex

        first_face = True
        # until there are element faces to be analyzed
        while faces_elements:
            # pop an element_face to be analyzed
            face_elements = faces_elements.pop(0)
            # print("\n\n------------------\n\nanalyzing face: " + str(face_elements))

            # we are analyzing a new face, so create a new Face object, unless this is the first face
            # being analyzed, in which case, take the exterior face of the DCEL
            face = self.exterior_face if first_face else Face()
            first_face = False
            # print("Face id: " + str(id(face)))

            # we assumed that we only deal with triangular faces, so each face will have exactly 3 edges
            # we put them in a list
            edges = list()

            # construct three edges (e0, e1, e2) for this face such that e0 = (v0, v1), e1 = (v1, v2), e2 = (v2, v0)
            # or retrieve the corresponding partially constructed edges (if they exist)
            for i, element in enumerate(face_elements):
                source_element, target_element = element, face_elements[(i+1) % len(face_elements)]
                # print("\n\nanalyzing edge: " + str((source_element, target_element)))
                # print("list of incomplete Edge: " + str(incomplete_edges_by_source_and_target))
                # print("list of generated Vertex: " + str(created_vertices_by_element))

                # if this edge was previously generated as the twin of an edge of another face, get it from
                # the list of incomplete edges
                edge = incomplete_edges_by_source_and_target.pop((source_element, target_element), None)

                # create the Vertex objects (source and target) of this edge or retrieve them if they already exist
                target_vertex = created_vertices_by_element.get(target_element,
                                                                Vertex(Point3(*vertices[target_element])))

                source_vertex = created_vertices_by_element.get(source_element,
                                                                Vertex(Point3(*vertices[source_element])))

                # if the edge we looked for exists, get its
                # - target Vertex
                # - source Vertex and
                # - twin_edge Edge
                if isinstance(edge, Edge):
                    # get the twin of this edge
                    twin_edge = edge.twin_edge
                else:
                    # prepare two new Edge objects for the edge and its twin
                    edge, twin_edge = Edge(), Edge()

                # complete the vertex by setting (or overwriting) the outgoing_edge
                target_vertex.outgoing_edge = twin_edge
                source_vertex.outgoing_edge = edge

                # put the vertices in the created_vertices dictionary
                # (or overwrite them if they were already in)
                created_vertices_by_element[target_element] = target_vertex
                created_vertices_by_element[source_element] = source_vertex

                # set the face of this edge
                edge.adjacent_face = face

                # set the target vertices
                edge.target_vertex = target_vertex
                twin_edge.target_vertex = source_vertex

                # link the twins
                edge.twin_edge = twin_edge
                twin_edge.twin_edge = edge

                # if the twin is not yet completed (which would be the case if it was generated as an edge of
                # a face analyzed previously) it will not be completed during the analysis of this face,
                # so place it in the incomplete_edges list
                if not twin_edge.is_complete():
                    incomplete_edges_by_source_and_target[(target_element, source_element)] = twin_edge

                # complete the edge
                if i > 0:
                    edge.prev_edge = edges[i-1]
                    edges[i-1].next_edge = edge

                # put the edge in the edges list of this face
                edges.append(edge)

            # the last and first edges are not yet linked to each other, link them
            edges[0].prev_edge = edges[-1]
            edges[-1].next_edge = edges[0]

            # finally, assign one of the edges of this face to the Face object
            face.adjacent_edges.append(edges[0])
Example #19
0
    def get_renderable_arrays(self):
        """
        traverse the DCEL structure and return a list of vertices and a list of elements for the rendering of the DCEL
        :return: vertices: a list of verices [a, b, c, ...],
        elements: a list of indices [idx_1, idx_2, idx_3, ...] from the list vertices.
         if the DCEL represents a set of point, each index refers a point to be rendered
         if the DCEL represents a linear entity, each pair of indices forms a segment to be rendered
         if the DCEL represents a polygon or a polyhedron, each triplet of indices form a face to be rendered
        """

        from app.geoms.point import Point3
        vertices = list()
        elements = list()
        outline_elements = list()

        if self.exterior_face.isolated_vertices:
            # if there are isolated vertices it means this DCEL represents a set of points (and nothing more)
            for v in self.exterior_face.isolated_vertices:
                elements.append(len(vertices))
                vertices.append(v)
        else:
            # otherwise we are treating either a linear geometry, a polygon, or a polyhedron
            visited_faces = set()

            faces_to_visit = set()
            faces_to_visit.add(self.exterior_face)

            while faces_to_visit:
                face = faces_to_visit.pop()
                if face not in visited_faces:

                    for ae in face.adjacent_edges:
                        edge_chain = ae.get_chain()

                        # if the first edge in the chain has no prev_edge the chain is open
                        open_chain = True if _is_chain_open(edge_chain) else False

                        v1_idx = None
                        first_edge, last_edge = 0, len(edge_chain)-1
                        # for i, edge in enumerate(reversed(edge_chain)):
                        #     # NOTE: OpenGL requires vertices of faces to be given in CW order, but our polygons
                        #     # have been constructed specifying vertices in CCW order, so we need to traverse
                        #     # the edge_chain in reverse order to create CW ordered vertices and elements
                        for i, edge in enumerate(edge_chain):
                            if edge.twin_edge.adjacent_face not in visited_faces and \
                                            edge.twin_edge.adjacent_face is not face:
                                faces_to_visit.add(edge.twin_edge.adjacent_face)

                            if i == first_edge:
                                v1 = edge.source_vertex()
                                if v1 in vertices:
                                    v1_idx = vertices.index(v1)
                                else:
                                    v1_idx = len(vertices)
                                    vertices.append(v1)

                            v2 = edge.target_vertex
                            if v2 in vertices:
                                v2_idx = vertices.index(v2)
                            else:
                                v2_idx = len(vertices)
                                vertices.append(v2)

                            if open_chain:
                                # if we are treating an open chain it means we are treating a linear entity
                                # so the elements must be given pair-wise. Each pair denotes the start and the
                                # end of the i-th segment making the linear entity
                                elements.extend([v1_idx, v2_idx])
                            else:
                                # otherwise we are treating a face. Faces can be only triangles (we assumed this before)
                                if i == first_edge:
                                    elements.append(v1_idx)

                                # the last vertex of the last edge is the first vertex of the first edge,
                                # so it was already treated
                                if i != last_edge:
                                    elements.append(v2_idx)

                                # treat the outline
                                # take the neighbor face (adjacent to the twin of this edge)
                                # access vertex opposite to the twin (REMEMBER: each face is a triangle)
                                # check if such a vertex is COPLANAR with

                                # get the face adjacent to this face through this edge
                                neighbor_face = edge.twin_edge.adjacent_face

                                # we proceed only if the neighbor face has been visited already
                                # in this way we avoid to add more than once the same part of the outline
                                if neighbor_face in visited_faces:
                                    # get the vertex opposite to this edge in the
                                    # face adjacent to this face through the current edge
                                    other_face_vertex = edge.twin_edge.next_edge.target_vertex

                                    # get the three vertices defining the face the current edge belongs to
                                    v3 = edge_chain[(i+1) % len(edge_chain)].target_vertex

                                    if not Point3.are_coplanar(v1.point, v2.point,
                                                                   v3.point, other_face_vertex.point):
                                        outline_elements.extend([v1_idx, v2_idx])

                            v1, v1_idx = v2, v2_idx  # advance the first vertex

                    visited_faces.add(face)

        return vertices, elements, outline_elements
Example #20
0
    def intersects(self, other):
        """
        :param other: Line or Vector
        :return: True if the two segments intersect, False otherwise
        """

        if isinstance(other, (Vector2, Vector3)):
            other = Line([Point3(0, 0, 0), Point3(other)])

        if not isinstance(other, Line):
            raise TypeError(
                "Line.intersects requires a Line or a Vector as parameter")

        l1, l2 = self, other  # handy aliases for the segments

        # in order for two segments to intersect they must lie on the same plane
        if not Point3.are_coplanar(l1.start, l1.end, l2.start, l2.end):
            print("Segments are not COPLANAR")
            return False

        # Ok, the segments are COPLANAR!
        # now, we have two cases to be treated separately: (1) the segments are COLLINEAR, (2) or not

        if l1.is_collinear_with(l2):
            # if COLLINEAR we need to check the relative distances between the extremes of the segments

            # We have only three possible cases where the segments intersect:
            # A) l2 is inside l1 (i.e., both l2 extremes are BETWEEN l1 extremes)
            # B) l1 is inside l2
            # C) none of the above, but one extreme of one of the two segments is BETWEEN the extremes of the other

            # compute the positions of the segments' extremes wrt the other segment
            l2_start_position = Point3.collinear_position(
                l1.start, l1.end, l2.start)
            l2_end_position = Point3.collinear_position(
                l1.start, l1.end, l2.end)
            l1_start_position = Point3.collinear_position(
                l2.start, l2.end, l1.start)
            l1_end_position = Point3.collinear_position(
                l2.start, l2.end, l1.end)

            # case A)
            if l2_start_position == BETWEEN and l2_end_position == BETWEEN:
                return True
            # case B)
            elif l1_start_position == BETWEEN and l1_end_position == BETWEEN:
                return True
            # case C)
            elif any(pos == BETWEEN for pos in [
                    l2_start_position, l2_end_position, l1_start_position,
                    l1_end_position
            ]):
                return True
            else:
                return False

        else:
            # if the two segments are not COLLINEAR, it can still be the case that one of the extremes of
            # one segment is COLLINEAR with the other segment. This case must be treated separately

            # consider l1 as reference segment and check if the extremes of l2 are COLLINEAR with l1
            is_l2_start_collinear = Point3.are_collinear(
                l1.start, l1.end, l2.start)
            is_l2_end_collinear = Point3.are_collinear(l1.start, l1.end,
                                                       l2.end)

            # consider l2 as reference segment and check if the extremes of l1 are COLLINEAR with l2
            is_l1_start_collinear = Point3.are_collinear(
                l2.start, l2.end, l1.start)
            is_l1_end_collinear = Point3.are_collinear(l2.start, l2.end,
                                                       l1.end)

            if is_l2_start_collinear or is_l2_end_collinear or is_l1_start_collinear or is_l1_end_collinear:
                # NOTE: at most one of the above variables can be True, otherwise it means that the two
                # segments are COLLINEAR and we would have entered the if condition above--l1.is_collinear_with(l2).

                # determine which is the reference segment
                ref_segment = l1 if is_l2_start_collinear or is_l2_end_collinear else l2

                # determine which extreme of the other segment is COLLINEAR with the ref_segment
                # assume it is l2.start
                coll_extreme = l2.start
                if ref_segment is l1:
                    if is_l2_end_collinear:
                        coll_extreme = l2.end
                else:
                    if is_l1_start_collinear:
                        coll_extreme = l1.start
                    else:
                        coll_extreme = l2.end

                # the only case when the two segments intersect is when the coll_extreme is positioned
                # BETWEEN the extremes of the reference segment
                return Point3.collinear_position(ref_segment.start,
                                                 ref_segment.end,
                                                 coll_extreme) == BETWEEN

            else:
                # the two segments are in general position. So, to determine if they intersect
                # we need to check the position of the extremes of each segment wrt the other segment.
                # To do this in 3-space we need an observation point that is not on the same plane
                # so, compute an observation point that is not on the same plane

                #  easily, we can take the cross product of the two segments
                v1 = Vector3(l1.end - l1.start)
                v2 = Vector3(l2.end - l2.start)
                v3 = v1.cross(v2)
                observation_point = Point3(v3) + l1.start

                # in order to intersect the extremes of each segment must lie on opposite sides of the other segment
                return \
                    l1.point_position(l2[0], observation_point) * l1.point_position(l2[1], observation_point) < 0 and \
                    l2.point_position(l1[0], observation_point) * l2.point_position(l1[1], observation_point) < 0
Example #21
0
    def intersection(self, other):
        # self= [p,p+r]; other=[q,q+other]
        if isinstance(other, Line):  # if other is actually a segment
            if self.intersects(other):  # if the segments intersect
                # print("INTERSECT")
                p = Vector3(self.vertices[0])
                q = Vector3(other.vertices[0])
                r = Vector3(self.vertices[1] - self.vertices[0])
                s = Vector3(other.vertices[1] - other.vertices[0])

                if not self.is_collinear_with(other):  # general case
                    # print("NOT COLLINEAR")

                    t = (q - p).cross(s)[2] / r.cross(s)[2]

                    return Point3(p.x() + t * r.x(), p.y() + t * r.y())

                else:  # special case: collinear segments
                    # print("COLLINEAR")

                    # position of other.vertices[0] wrt self.vertices[0],self.vertices[1]
                    s0_pos = Point3.collinear_position(self.vertices[0],
                                                       self.vertices[1],
                                                       other.vertices[0])

                    # position of other.vertices[1] wrt self.vertices[0],self.vertices[1]
                    s1_pos = Point3.collinear_position(self.vertices[0],
                                                       self.vertices[1],
                                                       other.vertices[1])

                    start = None
                    end = None

                    # if one vertex of other is before self
                    if s0_pos == BEFORE or s1_pos == BEFORE:
                        # print("ONE VERTEX OF S IS BEFORE")
                        start = self.vertices[
                            0]  # the resulting segment starts at self[0]
                        if s0_pos == BETWEEN:
                            # print("S[0] IS BETWEEN")
                            end = other.vertices[0]
                        elif s1_pos == BETWEEN:
                            # print("S[1] IS BETWEEN")
                            end = other.vertices[1]
                        elif s0_pos == AFTER or s1_pos == AFTER:
                            # print("THE OTHER VERTEX IS AFTER")
                            end = self.vertices[1]

                    # otherwise, at least one vertex of other must lie between self[0] and self[1]
                    # (because we know they intersect and are collinear)
                    else:
                        # print("ONE VERTEX OF S IS BETWEEN")
                        # assume other is completely inside self
                        start = other.vertices[
                            0]  # then the intersection coincides with other
                        end = other.vertices[1]

                        if s0_pos == AFTER:
                            start = self.vertices[
                                1]  # the intersection starts at self[1]
                        elif s1_pos == AFTER:
                            end = self.vertices[
                                1]  # the intersection ends at self[1]

                    # print("start:"+str(start))
                    # print("end:"+str(end))
                    if start != end:

                        return Line(vertices=[Point3(start), Point3(end)])
                    else:
                        return Point3(start)

        return None