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 distance(self, other): """ :param other: Point3 :return: the distance between self and other """ # NOTE: the distance is the magnitude of the vector from self to other return Vector3(other - self).magnitude()
def set_coords(self, x=None, y=None, z=None): if x is not None: Vector3.set_x(self, x) if y is not None: Vector3.set_y(self, y) if z is not None: Vector3.set_z(self, z) self.update_substructures()
def set_z(self, value): Vector3.set_z(self, value) self.update_substructures()
def __new__(cls, *args, **kwargs): # print("Point3.__new__") return Vector3.__new__(cls, *args, **kwargs) # No need to call Point constructor as it would not
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
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