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
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)
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)
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))
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)
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)
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)
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)
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)
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()
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
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()
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
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))
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)
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()
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()
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])
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
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
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