def intersection_plane_plane(plane1, plane2, tol=1e-6): """Computes the intersection of two planes Parameters ---------- plane1 : tuple The base point and normal (normalized) defining the 1st plane. plane2 : tuple The base point and normal (normalized) defining the 2nd plane. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- line : tuple Two points defining the intersection line. None if planes are parallel. """ o1, n1 = plane1 o2, n2 = plane2 if fabs(dot_vectors(n1, n2)) >= 1 - tol: return None # direction of intersection line d = cross_vectors(n1, n2) # vector in plane 1 perpendicular to the direction of the intersection line v1 = cross_vectors(d, n1) # point on plane 1 p1 = add_vectors(o1, v1) x1 = intersection_line_plane((o1, p1), plane2, tol=tol) x2 = add_vectors(x1, d) return x1, x2
def is_on_same_side(p1, p2, segment): a, b = segment v = subtract_vectors(b, a) c1 = cross_vectors(v, subtract_vectors(p1, a)) c2 = cross_vectors(v, subtract_vectors(p2, a)) if dot_vectors(c1, c2) >= 0: return True return False
def is_coplanar(points, tol=0.01): """Determine if the points are coplanar. Parameters ---------- points : sequence A sequence of locations in three-dimensional space. tol : float, optional A tolerance for planarity validation. Default is ``0.01``. Returns ------- bool ``True`` if the points are coplanar. ``False`` otherwise. Notes ----- Compute the normal vector (cross product) of the vectors formed by the first three points. Include one more vector at a time to compute a new normal and compare with the original normal. If their cross product is not zero, they are not parallel, which means the point are not in the same plane. Four points are coplanar if the volume of the tetrahedron defined by them is 0. Coplanarity is equivalent to the statement that the pair of lines determined by the four points are not skew, and can be equivalently stated in vector form as (x2 - x0).[(x1 - x0) x (x3 - x2)] = 0. """ tol2 = tol**2 if len(points) == 4: v01 = subtract_vectors(points[1], points[0]) v02 = subtract_vectors(points[2], points[0]) v23 = subtract_vectors(points[3], points[0]) res = dot_vectors(v02, cross_vectors(v01, v23)) return res**2 < tol2 # len(points) > 4 # compare length of cross product vector to tolerance a, b, c = sample(points, 3) u = subtract_vectors(b, a) v = subtract_vectors(c, a) w = cross_vectors(u, v) for i in range(0, len(points) - 2): u = v v = subtract_vectors(points[i + 2], points[i + 1]) wuv = cross_vectors(w, cross_vectors(u, v)) if wuv[0]**2 > tol2 or wuv[1]**2 > tol2 or wuv[2]**2 > tol2: return False return True
def circle_from_points(a, b, c): """Construct a circle from three points. Parameters ---------- a : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates. b : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates. c : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates. Returns ------- [float, float, float] Center of the circle. float Radius of the circle. [float, float, float] Normal of the plane containing the circle. Notes ----- For more information, see [1]_. References ---------- .. [1] Wikipedia. *Circumscribed circle*. Available at: https://en.wikipedia.org/wiki/Circumscribed_circle. Examples -------- >>> """ ab = subtract_vectors(b, a) cb = subtract_vectors(b, c) ba = subtract_vectors(a, b) ca = subtract_vectors(a, c) ac = subtract_vectors(c, a) bc = subtract_vectors(c, b) normal = normalize_vector(cross_vectors(ab, ac)) d = 2 * length_vector_sqrd(cross_vectors(ba, cb)) A = length_vector_sqrd(cb) * dot_vectors(ba, ca) / d B = length_vector_sqrd(ca) * dot_vectors(ab, cb) / d C = length_vector_sqrd(ba) * dot_vectors(ac, bc) / d Aa = scale_vector(a, A) Bb = scale_vector(b, B) Cc = scale_vector(c, C) center = sum_vectors([Aa, Bb, Cc]) radius = length_vector(subtract_vectors(a, center)) return center, radius, normal
def is_coplanar(points, tol=0.01): """Determine if the points are coplanar. Parameters ---------- points : list of points A sequence of point locations. tol : float, optional A tolerance for planarity validation. Default is ``0.01``. Returns ------- bool ``True`` if the points are coplanar. ``False`` otherwise. Notes ----- Compute the normal vector (cross product) of the vectors formed by the first three points. Include one more vector at a time to compute a new normal and compare with the original normal. If their cross product is not zero, they are not parallel, which means the point are not in the same plane. Four points are coplanar if the volume of the tetrahedron defined by them is 0. Coplanarity is equivalent to the statement that the pair of lines determined by the four points are not skew, and can be equivalently stated in vector form as (x2 - x0).[(x1 - x0) x (x3 - x2)] = 0. """ if len(points) < 4: return True tol2 = tol ** 2 if len(points) == 4: v01 = subtract_vectors(points[1], points[0]) v02 = subtract_vectors(points[2], points[0]) v23 = subtract_vectors(points[3], points[2]) res = dot_vectors(v02, cross_vectors(v01, v23)) return res**2 < tol2 a, b, c = points[:3] ab = subtract_vectors(b, a) n0 = cross_vectors(ab, subtract_vectors(c, a)) points = points[3:] for c in points: n1 = cross_vectors(ab, subtract_vectors(c, a)) if length_vector_sqrd(cross_vectors(n0, n1)) > tol: return False return True
def is_intersection_line_line(l1, l2, tol=1e-6): """Verifies if two lines intersect. Parameters ---------- l1 : [point, point] | :class:`compas.geometry.Line` A line. l2 : [point, point] | :class:`compas.geometry.Line` A line. tol : float, optional A tolerance for intersection verification. Returns -------- bool True if the lines intersect in one point. False if the lines are skew, parallel or lie on top of each other. """ a, b = l1 c, d = l2 e1 = normalize_vector(subtract_vectors(b, a)) e2 = normalize_vector(subtract_vectors(d, c)) # check for parallel lines if abs(dot_vectors(e1, e2)) > 1.0 - tol: return False # check for intersection if abs(dot_vectors(cross_vectors(e1, e2), subtract_vectors(c, a))) < tol: return True return False
def normal_triangle(triangle, unitized=True): """Compute the normal vector of a triangle. Parameters ---------- triangle : [point, point, point] | :class:`compas.geometry.Polygon` A list of triangle point coordinates. unitized : bool, optional If True, unitize the normal vector. Returns ------- [float, float, float] The normal vector. Raises ------ AssertionError If the triangle does not have three vertices. """ assert len(triangle) == 3, "Three points are required." a, b, c = triangle ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) if not unitized: return n lvec = 1 / length_vector(n) return [lvec * n[0], lvec * n[1], lvec * n[2]]
def intersection_line_triangle(line, triangle, tol=1e-6): """Computes the intersection point of a line (ray) and a triangle based on the Moeller Trumbore intersection algorithm Parameters ---------- line : tuple Two points defining the line. triangle : list of list of float XYZ coordinates of the triangle corners. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- point : tuple The intersectin point. None If the intersection does not exist. """ a, b, c = triangle ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) plane = a, n x = intersection_line_plane(line, plane, tol=tol) if x: if is_point_in_triangle(x, triangle): return x
def distance_point_line_sqrd(point, line): """Compute the squared distance between a point and a line. Parameters ---------- point : sequence of float XYZ coordinates of the point. line : list, tuple Line defined by two points. Returns ------- float The squared distance between the point and the line. Notes ----- For more info, see [1]_. References ---------- .. [1] Wikipedia. *Distance from a point to a line*. Available at: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line. """ a, b = line ab = subtract_vectors(b, a) pa = subtract_vectors(a, point) pb = subtract_vectors(b, point) length = length_vector_sqrd(cross_vectors(pa, pb)) length_ab = length_vector_sqrd(ab) return length / length_ab
def normal_triangle(triangle, unitized=True): """Compute the normal vector of a triangle. Parameters ---------- triangle : list of list A list of triangle point coordinates. Returns ------- list The normal vector. Raises ------ ValueError If the triangle does not have three vertices. """ assert len(triangle) == 3, "Three points are required." a, b, c = triangle ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) if not unitized: return n lvec = 1 / length_vector(n) return [lvec * n[0], lvec * n[1], lvec * n[2]]
def is_intersection_line_line(l1, l2, tol=1e-6): """Verifies if two lines intersect. Parameters ---------- l1 : tuple A sequence of XYZ coordinates of two 3D points representing two points on the line. l2 : tuple A sequence of XYZ coordinates of two 3D points representing two points on the line. tol : float, optional A tolerance for intersection verification. Default is ``1e-6``. Returns -------- bool ``True``if the lines intersect in one point. ``False`` if the lines are skew, parallel or lie on top of each other. """ a, b = l1 c, d = l2 e1 = normalize_vector(subtract_vectors(b, a)) e2 = normalize_vector(subtract_vectors(d, c)) # check for parallel lines if abs(dot_vectors(e1, e2)) > 1.0 - tol: return False # check for intersection d_vector = cross_vectors(e1, e2) if dot_vectors(d_vector, subtract_vectors(c, a)) == 0: return True return False
def intersection_line_line(l1, l2, tol=1e-6): """Computes the intersection of two lines. Parameters ---------- l1 : tuple, list XYZ coordinates of two points defining the first line. l2 : tuple, list XYZ coordinates of two points defining the second line. tol : float, optional A tolerance for membership verification. Default is ``1e-6``. Returns ------- list XYZ coordinates of the two points marking the shortest distance between the lines. If the lines intersect, these two points are identical. If the lines are skewed and thus only have an apparent intersection, the two points are different. If the lines are parallel, the return value is [None, None]. Examples -------- >>> """ a, b = l1 c, d = l2 ab = subtract_vectors(b, a) cd = subtract_vectors(d, c) n = cross_vectors(ab, cd) n1 = normalize_vector(cross_vectors(ab, n)) n2 = normalize_vector(cross_vectors(cd, n)) plane_1 = (a, n1) plane_2 = (c, n2) i1 = intersection_line_plane(l1, plane_2, tol=tol) i2 = intersection_line_plane(l2, plane_1, tol=tol) return i1, i2
def is_polygon_convex(polygon): """Determine if a polygon is convex. Parameters ---------- polygon : sequence of sequence of floats The XYZ coordinates of the corners of the polygon. Notes ----- Use this function for *spatial* polygons. If the polygon is in a horizontal plane, use :func:`is_polygon_convex_xy` instead. Returns ------- bool ``True`` if the polygon is convex. ``False`` otherwise. See Also -------- is_polygon_convex_xy Examples -------- >>> polygon = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.4, 0.4, 0.0], [0.0, 1.0, 0.0]] >>> is_polygon_convex(polygon) False """ a = polygon[0] o = polygon[1] b = polygon[2] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) for a, o, b in window(polygon + polygon[:2], 3): oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) if dot_vectors(n, n0) >= 0: continue else: return False return True
def is_polygon_convex(polygon): """Determine if a polygon is convex. Parameters ---------- polygon : sequence[point] | :class:`compas.geometry.Polygon` A polygon. Returns ------- bool True if the polygon is convex. False otherwise. Notes ----- Use this function for *spatial* polygons. If the polygon is in a horizontal plane, use :func:`is_polygon_convex_xy` instead. Examples -------- >>> polygon = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.4, 0.4, 0.0], [0.0, 1.0, 0.0]] >>> is_polygon_convex(polygon) False """ a = polygon[0] o = polygon[1] b = polygon[2] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) for a, o, b in window(polygon + polygon[:2], 3): oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) if dot_vectors(n, n0) >= 0: continue else: return False return True
def normal_polygon(polygon, unitized=True): """Compute the normal of a polygon defined by a sequence of points. Parameters ---------- polygon : list of list A list of polygon point coordinates. Returns ------- list The normal vector. Raises ------ ValueError If less than three points are provided. Notes ----- The points in the list should be unique. For example, the first and last point in the list should not be the same. """ p = len(polygon) assert p > 2, "At least three points required" nx = 0 ny = 0 nz = 0 o = centroid_points(polygon) a = polygon[-1] oa = subtract_vectors(a, o) for i in range(p): b = polygon[i] ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) oa = ob nx += n[0] ny += n[1] nz += n[2] if not unitized: return nx, ny, nz length = length_vector([nx, ny, nz]) return nx / length, ny / length, nz / length
def normal_polygon(polygon, unitized=True): """Compute the normal of a polygon defined by a sequence of points. Parameters ---------- polygon : sequence[point] | :class:`compas.geometry.Polygon` A list of polygon point coordinates. unitized : bool, optional If True, unitize the normal vector. Returns ------- [float, float, float] The normal vector. Raises ------ AssertionError If less than three points are provided. Notes ----- The points in the list should be unique. For example, the first and last point in the list should not be the same. """ p = len(polygon) assert p > 2, "At least three points required" nx = 0 ny = 0 nz = 0 o = centroid_points(polygon) a = polygon[-1] oa = subtract_vectors(a, o) for i in range(p): b = polygon[i] ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) oa = ob nx += n[0] ny += n[1] nz += n[2] if not unitized: return [nx, ny, nz] return normalize_vector([nx, ny, nz])
def area_polygon(polygon): """Compute the area of a polygon. Parameters ---------- polygon : sequence[point] | :class:`compas.geometry.Polygon` The XYZ coordinates of the vertices/corners of the polygon. The vertices are assumed to be in order. The polygon is assumed to be closed: the first and last vertex in the sequence should not be the same. Returns ------- float The area of the polygon. Examples -------- >>> """ o = centroid_points(polygon) a = polygon[-1] b = polygon[0] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) area = 0.5 * length_vector(n0) for i in range(0, len(polygon) - 1): oa = ob b = polygon[i + 1] ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) if dot_vectors(n, n0) > 0: area += 0.5 * length_vector(n) else: area -= 0.5 * length_vector(n) return area
def distance_line_line(l1, l2, tol=0.0): r"""Compute the shortest distance between two lines. Parameters ---------- l1 : tuple Two points defining a line. l2 : tuple Two points defining a line. Returns ------- float The distance between the two lines. Notes ----- The distance is the absolute value of the dot product of a unit vector that is perpendicular to the two lines, and the vector between two points on the lines ([1]_, [2]_). If each of the lines is defined by two points (:math:`l_1 = (\mathbf{x_1}, \mathbf{x_2})`, :math:`l_2 = (\mathbf{x_3}, \mathbf{x_4})`), then the unit vector that is perpendicular to both lines is... References ---------- .. [1] Weisstein, E.W. *Line-line Distance*. Available at: http://mathworld.wolfram.com/Line-LineDistance.html. .. [2] Wikipedia. *Skew lines Distance*. Available at: https://en.wikipedia.org/wiki/Skew_lines#Distance. Examples -------- >>> """ a, b = l1 c, d = l2 ab = subtract_vectors(b, a) cd = subtract_vectors(d, c) ac = subtract_vectors(c, a) n = cross_vectors(ab, cd) length = length_vector(n) if length <= tol: return distance_point_point(closest_point_on_line(l1[0], l2), l1[0]) n = scale_vector(n, 1.0 / length) return fabs(dot_vectors(n, ac))
def angle_vectors_signed(u, v, normal, deg=False, threshold=1e-3): """Computes the signed angle between two vectors. It calculates the angle such that rotating vector u about the normal by angle would result in a vector that looks into the same direction as v. Parameters ---------- u : [float, float, float] | :class:`compas.geometry.Vector` XYZ components of the first vector. v : [float, float, float] | :class:`compas.geometry.Vector` XYZ components of the second vector. normal : [float, float, float] | :class:`compas.geometry.Vector` XYZ components of the plane's normal spanned by u and v. deg : bool, optional If True, returns the angle in degrees. threshold : float, optional The threshold (radians) used to consider if the angle is zero. Returns ------- float The signed angle in radians (in degrees if deg == True). Examples -------- >>> normal = [0.0, 0.0, 1.0] >>> angle_vectors_signed([0.0, 1.0, 0.0], [1.0, 0.0, 0.0], normal) -1.57079 """ angle = angle_vectors(u, v) normal_uv = cross_vectors(u, v) if length_vector(normal_uv) > threshold: # check if normal_uv has the same direction as normal angle_btw_normals = angle_vectors(normal, normal_uv) if angle_btw_normals > threshold: angle *= -1 if deg: return degrees(angle) else: return angle
def distance_point_line(point, line): """Compute the distance between a point and a line. Parameters ---------- point : list, tuple Point location. line : list, tuple Line defined by two points. Returns ------- float The distance between the point and the line. Notes ----- This implementation computes the *right angle distance* from a point P to a line defined by points A and B as twice the area of the triangle ABP divided by the length of AB [1]_. References ---------- .. [1] Wikipedia. *Distance from a point to a line*. Available at: https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line Examples -------- >>> """ a, b = line ab = subtract_vectors(b, a) pa = subtract_vectors(a, point) pb = subtract_vectors(b, point) length = length_vector(cross_vectors(pa, pb)) length_ab = length_vector(ab) return length / length_ab
def volume_polyhedron(polyhedron): r"""Compute the volume of a polyhedron represented by a closed mesh. Parameters ---------- polyhedron : tuple[sequence[[float, float, float] | :class:`compas.geometry.Point`], sequence[sequence[int]]] The vertices and faces of the polyhedron. Returns ------- float The volume of the polyhedron. Notes ----- This implementation is based on the divergence theorem, the fact that the *area vector* is constant for each face, and the fact that the area of each face can be computed as half the length of the cross product of two adjacent edge vectors [1]_. .. math:: :nowrap: \begin{align} V = \int_{P} 1 &= \frac{1}{3} \int_{\partial P} \mathbf{x} \cdot \mathbf{n} \\ &= \frac{1}{3} \sum_{i=0}^{N-1} \int{A_{i}} a_{i} \cdot n_{i} \\ &= \frac{1}{6} \sum_{i=0}^{N-1} a_{i} \cdot \hat n_{i} \end{align} Warnings -------- The volume computed by this funtion is only correct if the polyhedron is convex, has planar faces, and is positively oriented (all face normals point outwards). References ---------- .. [1] Nurnberg, R. *Calculating the area and centroid of a polygon in 2d*. Available at: http://wwwf.imperial.ac.uk/~rn/centroid.pdf """ xyz, faces = polyhedron V = 0 for vertices in faces: if len(vertices) == 3: triangles = [vertices] else: centroid = centroid_points([xyz[i] for i in vertices]) i = len(xyz) xyz.append(centroid) triangles = [] for u, v in pairwise(vertices + vertices[0:1]): triangles.append([i, u, v]) for u, v, w in triangles: a = xyz[u] b = xyz[v] c = xyz[w] ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) V += dot_vectors(a, n) return V / 6.
def centroid_polygon(polygon): r"""Compute the centroid of the surface of a polygon. Parameters ---------- polygon : list of point A sequence of polygon point coordinates. Returns ------- list The XYZ coordinates of the centroid. Notes ----- The centroid is the centre of gravity of the polygon surface if mass would be uniformly distributed over it. It is calculated by triangulating the polygon surface with respect to the centroid of the polygon vertices, and then computing the centroid of the centroids of the individual triangles, weighted by the corresponding triangle area in proportion to the total surface area. .. math:: c_x = \frac{1}{A} \sum_{i=1}^{N} A_i \cdot c_{x,i} c_y = \frac{1}{A} \sum_{i=1}^{N} A_i \cdot c_{y,i} c_z = \frac{1}{A} \sum_{i=1}^{N} A_i \cdot c_{z,i} Warning ------- The polygon need not be convex. The polygon need not be flat. However, it is unclear what the meaning of the centroid is in that case. The polygon may be self-intersecting. However, it is unclear what the meaning of the centroid is in that case. Examples -------- >>> polygon = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.0, 1.0, 0.0]] >>> centroid_polygon(polygon) """ p = len(polygon) assert p > 2, "At least three points required" if p == 3: return centroid_points(polygon) cx, cy, cz = 0.0, 0.0, 0.0 A2 = 0 o = centroid_points(polygon) a = polygon[-1] b = polygon[0] oa = subtract_vectors(a, o) ob = subtract_vectors(b, o) n0 = cross_vectors(oa, ob) x, y, z = centroid_points([o, a, b]) a2 = length_vector(n0) A2 += a2 cx += a2 * x cy += a2 * y cz += a2 * z for i in range(1, len(polygon)): a = b b = polygon[i] oa = ob ob = subtract_vectors(b, o) n = cross_vectors(oa, ob) x, y, z = centroid_points([o, a, b]) if dot_vectors(n, n0) > 0: a2 = length_vector(n) else: a2 = -length_vector(n) A2 += a2 cx += a2 * x cy += a2 * y cz += a2 * z if A2 == 0: return polygon[0] return [cx / A2, cy / A2, cz / A2]
def is_intersection_line_triangle(line, triangle, tol=1e-6): """Verifies if a line (ray) intersects with a triangle. Parameters ---------- line : [point, point] | :class:`compas.geometry.Line` A line. triangle : [point, point, point] A triangle. tol : float, optional A tolerance for intersection verification. Returns ------- bool True if the line (ray) intersects with the triangle. False otherwise. Notes ----- Based on the Moeller Trumbore intersection algorithm. The line is treated as continues, directed ray and not as line segment with a start and end point Examples -------- >>> """ a, b, c = triangle # direction vector and base point of line v1 = subtract_vectors(line[1], line[0]) p1 = line[0] # Find vectors for two edges sharing triangle vertex 1 e1 = subtract_vectors(b, a) e2 = subtract_vectors(c, a) # Begin calculating determinant - also used to calculate u parameter p = cross_vectors(v1, e2) # if determinant is near zero, ray lies in plane of triangle det = dot_vectors(e1, p) # NOT CULLING if det > -tol and det < tol: return False inv_det = 1.0 / det # calculate distance from V1 to ray origin t = subtract_vectors(p1, a) # Calculate u parameter and make_blocks bound u = dot_vectors(t, p) * inv_det # The intersection lies outside of the triangle if u < 0.0 or u > 1.0: return False # Prepare to make_blocks v parameter q = cross_vectors(t, e1) # Calculate V parameter and make_blocks bound v = dot_vectors(v1, q) * inv_det # The intersection lies outside of the triangle if v < 0.0 or u + v > 1.0: return False t = dot_vectors(e2, q) * inv_det if t > tol: return True # No hit return False
def centroid_polyhedron(polyhedron): """Compute the center of mass of a polyhedron. Parameters ---------- polyhedron : tuple The coordinates of the vertices, and the indices of the vertices forming the faces. Returns ------- list XYZ coordinates of the center of mass. Warning ------- This function assumes that the vertex cycles of the faces are such that the face normals are consistently pointing outwards, resulting in a *positive* polyhedron. Examples -------- >>> from compas.geometry._core import Polyhedron >>> p = Polyhedron.generate(6) >>> centroid_polyhedron(p) [0.0, 0.0, 0.0] """ vertices, faces = polyhedron V = 0 x = 0.0 y = 0.0 z = 0.0 ex = [1.0, 0.0, 0.0] ey = [0.0, 1.0, 0.0] ez = [0.0, 0.0, 1.0] for face in faces: if len(face) == 3: triangles = [face] else: centroid = centroid_points([vertices[index] for index in face]) w = len(vertices) vertices.append(centroid) triangles = [[w, u, v] for u, v in pairwise(face + face[0:1])] for triangle in triangles: a = vertices[triangle[0]] b = vertices[triangle[1]] c = vertices[triangle[2]] ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) n = cross_vectors(ab, ac) V += dot_vectors(a, n) nx = dot_vectors(n, ex) ny = dot_vectors(n, ey) nz = dot_vectors(n, ez) ab = add_vectors(a, b) bc = add_vectors(b, c) ca = add_vectors(c, a) ab_x2 = dot_vectors(ab, ex)**2 bc_x2 = dot_vectors(bc, ex)**2 ca_x2 = dot_vectors(ca, ex)**2 x += nx * (ab_x2 + bc_x2 + ca_x2) ab_y2 = dot_vectors(ab, ey)**2 bc_y2 = dot_vectors(bc, ey)**2 ca_y2 = dot_vectors(ca, ey)**2 y += ny * (ab_y2 + bc_y2 + ca_y2) ab_z2 = dot_vectors(ab, ez)**2 bc_z2 = dot_vectors(bc, ez)**2 ca_z2 = dot_vectors(ca, ez)**2 z += nz * (ab_z2 + bc_z2 + ca_z2) # for j in (-1, 0, 1): # ab = add_vectors(vertices[triangle[j]], vertices[triangle[j + 1]]) # x += nx * dot_vectors(ab, ex) ** 2 # y += ny * dot_vectors(ab, ey) ** 2 # z += nz * dot_vectors(ab, ez) ** 2 V = V / 6.0 if V < 1e-9: d = 1.0 / (2 * 24) else: d = 1.0 / (2 * 24 * V) x *= d y *= d z *= d return [x, y, z]