""" --- Description --- This Python code is for calculating the area of intersection of a triangle and a circle in R^2 for the case that the center of the circle coincides with a vertex of the triangle. The area of non-intersection of the triangle is also given. The radius of the circle and three triangle vertex coordinates v1, v2, and v3 should be provided and v3 should coincide with the center of the circle. This code also includes information for generating a network of which pores are components. --- Strategy --- The triangle and the circle are translated so that v3 and the center of the circle coincide with ordinate o. Hereinafter, v1, v2, v3 refer to the translated coordinates. All five cases of intersection are considered: - Case 1 - sd_o_vf <= scr, in which scr and sd_o_vf denote the squared circle radius and the squared distance from ordinate o to the vertex vf furthest from the ordinate. In this case, the area of intersection is equal to the area of the triangle. - Case 2 - sd_o_vc <= scr < sd_o_vf, in which sd_o_vc denotes the squared distance from ordinate o to the vertex vc closest to the ordinate, not taking into account v3. In this case, there is one intersection point of the circle with the line segment defined by v1 and v2. The intersecting area is the sum of the area of a circle segment and a triangle that have the line segment defined by o and the intersection point of the circle and the line segment defined by v1 and v2 in common. - Cases 3 & 4 - sd_o_vc > scr > sd_o_a, in which sd_o_a denotes the squared distance from o to a point a on the line defined by v1 and v2. Point a is the point on the line closest to o. Case 3: The line segment defined by v1 and v2 has two intersection points with the circle. Therefore, the intersecting area of the triangle with the circle consists of two circle segments and one triangle. Case 4: The line segment defined by v1 and v2 does not intersect the circle. Therefore, the intersecting area of the triangle with the circle consists of one circle segment. - Case 5 - scr <= sd_o_a The intersecting area of the triangle with the circle consists of one circle segment. In cases 2 and 3, v1 and v2 are rotated with respect to ordinate o so that the line defined by v1 and v2 is parallel to the x-axis and located on the positive side of the y-axis. This is done to avoid dealing with the two semi-circle functions that describe the circle while calculating intersections. """ import math import matplotlib.pyplot as plt def squared_distance_o_a(v1_x, v1_y, v2_x, v2_y): """ --- Calculating sd_o_a --- The squared distance, shortest, from ordinate o to the line formed by v1 and v2 is calculated. The calculation is done using basic vector calculus. """ divided = (v1_x * v2_y - v2_x * v1_y) ** 2 divisor = ((v2_x - v1_x) ** 2 + (v2_y - v1_y) ** 2) # Handling division by zero. if divisor == 0.0: return 0.0 else: return divided / divisor def vector_ob(v1_x, v1_y, v2_x, v2_y): """ --- Calculating the vector ob --- Point b lays on the line defined by points o and a. Vector ob is defined by initial point o and terminal point b, and vector 12 is defined by initial point v1 and terminal point v2. Arbitrarily, we choose the magnitude of the x-component b_x of vector ob equal to unity. To find the direction of vector ob, we consider cases: b_x = 1 = b_x_p and b_x = -1 = b_x_n. The corresponding y-coordinates, b_y_p and b_y_n are calculated by setting the dot product of vector 12 and vector ob to zero. If sd_v1_b_p < sd_v1_b_n, b_x = b_x_p, else, b_x = b_x_n. sd_v1_b_p denotes the squared distance of the line segment defined by v1 and e, with b_x_p. sd_v1_b_n denotes the squared distance of the line segment defined by v1 and e, with b_x_n. """ # Handling division by zero. if v2_y - v1_y == 0: if v1_y > 0: b_x = 0 b_y = 1 else: b_x = 0 b_y = -1 else: # Case b_x_p. b_x_p = 1 # Calculating the y-coordinate with the above-mentioned dot product equal to zero. b_y_p = (v1_x - v2_x) / (v2_y - v1_y) # Square distance of above-mentioned line segment. sd_v1_b_p = (b_x_p - v1_x) ** 2 + (b_y_p - v1_y) ** 2 # Case b_x_n. b_x_n = -1 b_y_n = (v2_x - v1_x) / (v2_y - v1_y) sd_v1_b_n = (b_x_n - v1_x) ** 2 + (b_y_n - v1_y) ** 2 # Determining whether b_x = b_x_p or b_x = b_x_n. if sd_v1_b_p < sd_v1_b_n: b_x = b_x_p b_y = b_y_p else: b_x = b_x_n b_y = b_y_n return b_x, b_y def rotation_ob(b_x, b_y, v1_x, v1_y, v2_x, v2_y, sd_o_a): """ This function rotates points v1 and v2 with respect to ordinate o so that the line segment defined by v1 and v2 is parallel to the x-axis and located on the positive side of the y-axis. After rotation, vector ob should point in the direction of the positive y-axis. sd_o_a denotes the squared distance of the line segment od defined by ordinate o and point d, which lays on the line defined by v1 and v2. Line segment od is perpendicular to that line. --- Calculating coordinates of v1 and v2 after rotation --- Since sd_o_a is known, the y-coordinates of v1 and v2 after rotation are also known, but the x-coordinates v1_r_x and v2_r_x of respective coordinates v1 and v2, after rotation, should be calculated. The magnitudes of v1_r_x and v2_r_x are equal to the distances from v1 to a and from v2 to d, respectively. The signs of v1_r_x and v2_r_x are determined using the vector product. For example, if the vector product of vector ob and vector o_v1 is positive, the x-coordinates of v1 after rotation is negative. Vector o_v1 is the vector defined by initial point o and terminal point v1. Vector o_v1 is defined by initial point o and terminal point v1. Vector o_v2 is defined by initial point o and terminal point v2. """ # Calculating v1_r_x squared and v2_r_x squared. v1_r_x_s = v1_x ** 2 + v1_y ** 2 - sd_o_a v2_r_x_s = v2_x ** 2 + v2_y ** 2 - sd_o_a # Handling negative values of v1_r_x_s and v2_r_x_s. if v1_r_x_s < 0: v1_r_x_s = 0 if v2_r_x_s < 0: v2_r_x_s = 0 # Obtaining the sign of the x_coordinate of v1 after rotation. if (b_x * v1_y - v1_x * b_y) > 0: v1_r_x = - v1_r_x_s ** 0.5 else: v1_r_x = v1_r_x_s ** 0.5 # Obtaining the sign of the x_coordinate of v2 after rotation. if (b_x * v2_y - v2_x * b_y) > 0: v2_r_x = - v2_r_x_s ** 0.5 else: v2_r_x = v2_r_x_s ** 0.5 return v1_r_x, v2_r_x def triangle_circle_area_intersection(v1, v2, v3, r): # Translating the vertex coordinates so that v3 coincides with ordinate o. v3_x = v3[0] # x-coordinate of v3 before translation v3_y = v3[1] # y-coordinate of v3 before translation v1_x = v1[0] - v3_x # x-coordinate of translated v1 v1_y = v1[1] - v3_y # y-coordinate of translated v1 v2_x = v2[0] - v3_x # x-coordinate of translated v2 v2_y = v2[1] - v3_y # y-coordinate of translated v2 # Plotting translated coordinates. plt.scatter([v1_x, v2_x, 0.0], [v1_y, v2_y, 0.0]) # Calculating squared distances from the ordinate to the translated vertices. # sd and o denote squared distance and ordinate, respectively. sd_o_v1 = v1_x ** 2 + v1_y ** 2 # sd_o_v1 denotes squared distance from ordinate o to v1 sd_o_v2 = v2_x ** 2 + v2_y ** 2 # sd_o_v2 denotes squared distance from ordinate o to v2 scr = r ** 2 # scr denotes squared circle radius # Calculating the area of the triangle defined by v1, v2 and v3, which is the maximum area of intersection # area_tri. area_tri = .5 * abs(v1_x * v2_y - v2_x * v1_y) # Handling division by zero. if sd_o_v1 == .0 or sd_o_v2 == .0 or r == .0: inter_area = .0 not_inter_area_v1 = area_tri # no_inter_area_v2 = .0 v1_v2_add_seg = True v1_filter = False v2_filter = False else: # Finding vf and vc. if sd_o_v2 < sd_o_v1: sd_o_vf = sd_o_v1 # sd_o_vf denotes squared distance form o to vf sd_o_vc = sd_o_v2 # sd_o_vc denotes squared distance form o to vc o_vf_equal_o_v1 = True # condition that appears hereinafter else: sd_o_vf = sd_o_v2 sd_o_vc = sd_o_v1 o_vf_equal_o_v1 = False # Calculating the squared distance, shortest, from ordinate o to the line formed by v1 and v2. sd_o_a = squared_distance_o_a(v1_x, v1_y, v2_x, v2_y) # Case 1: if sd_o_vf <= scr: # inter_area denotes the area of intersection with the triangle and the circle. inter_area = area_tri not_inter_area_v1 = .0 no_inter_area_v2 = .0 v1_v2_add_seg = False v1_filter = True v2_filter = True # Plotting the translated triangle. plt.plot([0, v1_x, v2_x, 0], [0, v1_y, v2_y, 0], color='gray') plt.title('Case 1') # Case 2: elif sd_o_vc <= scr: # Rotated vertices v1 and v2 are denoted v1_r and v2_r, respectively. # Calculating the y-values, which are identical, of rotated vertex coordinates. v_r_y_sq = sd_o_a v_r_y = sd_o_a ** .5 # v_r_y denotes the y_coordinate of the vertices v1 and v2 after rotation. # Calculating the x-values of the translated vertices after rotation. b_x, b_y = vector_ob(v1_x, v1_y, v2_x, v2_y) v1_r_x, v2_r_x = rotation_ob(b_x, b_y, v1_x, v1_y, v2_x, v2_y, sd_o_a) # Calculating the intersections with line y = v_r_y and translated circle. Only one intersection lays on # the line segment defined by v1_r and v2_r. is_p_x and is_n_x denote the x-coordinates of the # intersection that lay on the positive side of the x-axis and on the negative side of the x-axis, # respectively. is_p_x = (scr - sd_o_a) ** .5 is_n_x = -is_p_x # Obtaining the coordinates of the triangle with vertices o, vc, and i_s. This triangle is always defined # in the triangle with vertices o, vc, and vf. The circular sector is defined between the line segment # defined by points o and vf, the line segment defined by points o and i_s, and the translated circle. if o_vf_equal_o_v1: # same as: if o_vf = o_v1. # vf_x denotes the x_coordinate of vf vf_x = v1_r_x v3_x = v2_r_x if v1_r_x < .0: # same as: if vf_x is negative, i_s has a negative x-coordinate. # is_x denotes the x-coordinate of the intersection of the triangle and the circle. is_x = is_n_x # Calculating the area of the triangle with vertices o, vc, and i_s area_triangle = .5 * v_r_y * (v3_x - is_x) else: # same as: vf_x > 0.0 is_x = is_p_x area_triangle = .5 * v_r_y * (is_x - v3_x) # Calculating the area of the circle sector. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom = (is_x * vf_x + v_r_y_sq) / (r * sd_o_vf ** .5) if acos_dom < 1.0: angle = math.acos(acos_dom) else: angle = .0 area_sector = .5 * angle * r ** 2 # Calculating areas. inter_area = area_triangle + area_sector not_inter_area_v1 = area_tri - inter_area no_inter_area_v2 = .0 v1_filter = False v2_filter = True else: # same as: o_vf = o_v2. vf_x = v2_r_x v3_x = v1_r_x if vf_x < 0: is_x = is_n_x area_triangle = .5 * v_r_y * (v3_x - is_x) else: is_x = is_p_x area_triangle = .5 * v_r_y * (is_x - v3_x) # Calculating the area of the circle sector. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom = (is_x * vf_x + v_r_y_sq) / (r * sd_o_vf ** .5) if acos_dom < 1.0: angle = math.acos(acos_dom) else: angle = .0 area_sector = .5 * angle * r ** 2 # Calculating areas. inter_area = area_triangle + area_sector not_inter_area_v1 = .0 no_inter_area_v2 = area_tri - inter_area v1_filter = True v2_filter = False v1_v2_add_seg = False # Plotting. plt.plot([0, v1_r_x, v2_r_x, 0, is_x], [0, v_r_y, v_r_y, 0, v_r_y], color='gray') plt.scatter([v1_r_x, v2_r_x], [v_r_y, v_r_y]) plt.scatter(is_x, v_r_y, color='black') plt.title('Case 2') # Cases 3 and 4 elif scr > sd_o_a: # Rotated vertices v1 and v2 are denoted v1_r and v2_r, respectively. # Calculating the y-values, which are identical, of rotated vertex coordinates. # v_r_y denotes the y_coordinate of the vertices v1 and v2 after rotation. v_r_y_sq = sd_o_a v_r_y = sd_o_a ** .5 # Calculating b coordinates of vector ob b_x, b_y = vector_ob(v1_x, v1_y, v2_x, v2_y) # Calculating the intersections with line y = v_r_y and the circle. is_p_x = (scr - sd_o_a) ** .5 is_n_x = -is_p_x # Case 3 # There are two intersections between the triangle and the circle and v1 and v2 lay on opposing sides # of vector_ob. if (b_x * v1_y - b_y * v1_x) * (b_x * v2_y - b_y * v2_x) < .0: v1_r_x, v2_r_x = rotation_ob(b_x, b_y, v1_x, v1_y, v2_x, v2_y, sd_o_a) # v_r_p_x denotes the x-coordinate of the vertex that lays on the positive side of the x-axis and # v_r_n_x denotes the x-coordinate of the vertex that lays on the negative side of the x-axis. # Calculating area of the triangle defined by the two intersection points and o. area_triangle = v_r_y * is_p_x if v2_r_x < v1_r_x: # or v2 < 0 # Calculating the area of the circle sector on the negative side of the x-axis. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom_n = (is_n_x * v2_r_x + v_r_y_sq) / (r * sd_o_v2 ** .5) if acos_dom_n < 1.0: angle_n = math.acos(acos_dom_n) else: angle_n = .0 area_sector_n = .5 * angle_n * r ** 2 # Calculating the area of the circle sector on the positive side of the x-axis. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom_p = (is_p_x * v1_r_x + v_r_y_sq) / (r * sd_o_v1 ** .5) if acos_dom_p < 1.0: angle_p = math.acos(acos_dom_p) else: angle_p = .0 area_sector_p = .5 * angle_p * r ** 2 # Calculating areas. not_inter_area_v1 = (v1_r_x - is_p_x) * v_r_y * .5 - area_sector_p no_inter_area_v2 = (is_n_x - v2_r_x) * v_r_y * .5 - area_sector_n else: # v1_r_x < v2_r_x # Calculating the area of the circle sector on the negative side of the x-axis. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom_n = (is_n_x * v1_r_x + v_r_y_sq) / (r * sd_o_v1 ** .5) if acos_dom_n < 1.0: angle_n = math.acos(acos_dom_n) else: angle_n = .0 area_sector_n = .5 * angle_n * r ** 2 # Calculating the area of the circle sector on the positive side of the x-axis. # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom_p = (is_p_x * v2_r_x + v_r_y_sq) / (r * sd_o_v2 ** .5) if acos_dom_p < 1.0: angle_p = math.acos(acos_dom_p) else: angle_p = .0 area_sector_p = .5 * angle_p * r ** 2 # Calculating areas. not_inter_area_v1 = (is_n_x - v1_r_x) * v_r_y * .5 - area_sector_n no_inter_area_v2 = (v2_r_x - is_p_x) * v_r_y * .5 - area_sector_p inter_area = area_triangle + area_sector_n + area_sector_p v1_v2_add_seg = False v1_filter = False v2_filter = False # Plotting. plt.scatter(is_p_x, v_r_y, color='black') plt.scatter(is_n_x, v_r_y, color='black') plt.plot([0, v1_r_x, v2_r_x, 0, is_p_x], [0, v_r_y, v_r_y, 0, v_r_y], color='gray') plt.plot([0, is_n_x], [0, v_r_y], color='gray') plt.scatter([v1_r_x, v2_r_x], [v_r_y, v_r_y]) plt.title('Case 3') # Case 4 # No intersections and v1 and v2 lay on the same side of vector ob. else: # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom = (v1_x * v2_x + v1_y * v2_y) / (sd_o_v1 ** .5 * sd_o_v2 ** .5) if acos_dom < 1.0: angle = math.acos(acos_dom) else: angle = .0 inter_area = .5 * angle * r ** 2 not_inter_area_v1 = area_tri - inter_area no_inter_area_v2 = not_inter_area_v1 v1_v2_add_seg = True v1_filter = False v2_filter = False # plotting plt.plot([0, v1_x, v2_x, 0], [0, v1_y, v2_y, 0], color='gray') plt.title('Case 4') # case 5: scr <= sd_o_a else: # Handling values slightly larger than unity. # Division by zero is handled above. acos_dom = (v1_x * v2_x + v1_y * v2_y) / (sd_o_v1 ** .5 * sd_o_v2 ** .5) if acos_dom < 1.0: angle = math.acos(acos_dom) else: angle = .0 inter_area = .5 * angle * r ** 2 not_inter_area_v1 = area_tri - inter_area no_inter_area_v2 = not_inter_area_v1 v1_v2_add_seg = True v1_filter = False v2_filter = False # plotting plt.plot([0, v1_x, v2_x, 0], [0, v1_y, v2_y, 0], color='gray') plt.title('Case 5') # Handling areas of negative value. if area_tri < .0: area_tri = .0 if inter_area < .0: inter_area = .0 if not_inter_area_v1 < .0: not_inter_area_v1 = .0 if no_inter_area_v2 < .0: no_inter_area_v2 = .0 return area_tri, inter_area, not_inter_area_v1, no_inter_area_v2, v1_v2_add_seg, v1_filter, v2_filter # --- input --- # # following values can be used to generate specific cases of area overlap # v1 -> cases 1, 2, 3, 5: [-3.0, -1.0]; case 4: [-1.0, -3.0] # v2 -> all cases: [0.0, -4.0] # v3 -> all cases: [0.0, 0.0] # radius -> # case 1: 4.5, case 2: 3.3, case 3: 2.9 case 4: 2.9, case 5: 2.5 # # x and y coordinate of vertex 1 vertex1 = [-3.0, -1.0] # x and y coordinate of vertex 2 vertex2 = [0.0, -4.0] # x and y coordinate of vertex 3 vertex3 = [0.0, 0.0] # radius of the circle with its center located on vertex 3 radius = 4.5 # plotting a disk of radius equal to radius fig, ax = plt.subplots() circle = plt.Circle((0, 0), radius, color='#bfe6dd') c = ax.add_artist(circle) c.set_zorder(0) # calculate areas and obtaining information for generating a network of which pores are components tri_area, area_inter, area_not_v1, area_not_v2, add_seg, v1_o_d, v2_o_d \ = triangle_circle_area_intersection(vertex1, vertex2, vertex3, radius) print('area of intersection =', area_inter) print('add v1v2 segment =', add_seg) print('filter v1 =', v1_o_d) print('filter v2 =', v2_o_d) print('area of no intersection v1 =', area_not_v1) print('area of no intersection v2 =', area_not_v2) print('triangle area =', tri_area) # showing the plot plt.grid(True) plt.axis('scaled') ax.set(xlim=(-5, 5), ylim=(-5, 5)) plt.tight_layout() plt.show()