def area_polygon_xy(polygon): """Compute the area of a polygon lying in the XY-plane. Parameters ---------- polygon : sequence A sequence of XY(Z) coordinates of 2D or 3D points representing the locations of the corners of a 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. """ o = centroid_points_xy(polygon) u = subtract_vectors_xy(polygon[-1], o) v = subtract_vectors_xy(polygon[0], o) a = 0.5 * cross_vectors_xy(u, v)[2] for i in range(0, len(polygon) - 1): u = v v = subtract_vectors_xy(polygon[i + 1], o) a += 0.5 * cross_vectors_xy(u, v)[2] return abs(a)
def convex_hull_xy(points): """Computes the convex hull of a set of 2D points. Parameters ---------- points : list XY(Z) coordinates of the points. Returns ------- list XY(Z) coordinates of vertices of the convex hull in counter-clockwise order, starting from the vertex with the lexicographically smallest coordinates. Notes ----- Implements Andrew's monotone chain algorithm [1]_. O(n log n) complexity. References ---------- .. [1] Wiki Books. *Algorithm Implementation/Geometry/Convex hull/Monotone chain*. Available at: https://en.wikibooks.org/wiki/Algorithm_Implementation/Geometry/Convex_hull/Monotone_chain. """ # Sort the points lexicographically (tuples are compared lexicographically). # Remove duplicates to detect the case we have just one unique point. points = sorted(set(points)) # Boring case: no points or a single point, possibly repeated multiple times. if len(points) <= 1: return points # Build lower hull lower = [] for p in points: while len(lower) >= 2 and cross_vectors_xy(lower[-2], lower[-1], p) <= 0: lower.pop() lower.append(p) # Build upper hull upper = [] for p in reversed(points): while len(upper) >= 2 and cross_vectors_xy(upper[-2], upper[-1], p) <= 0: upper.pop() upper.append(p) # Concatenation of the lower and upper hulls gives the convex hull. # Last point of each list is omitted because it is repeated at the beginning of the other list. return lower[:-1] + upper[:-1]
def distance_point_line_sqrd_xy(point, line): """Compute the squared distance between a point and a line lying in the XY-plane. Parameters ---------- point : sequence of float XY(Z) coordinates of a 2D or 3D point (Z will be ignored). line : list, tuple Line defined by two points. Returns ------- float The squared distance between the point and the line. Notes ----- This implementation computes the orthogonal squared 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. """ a, b = line ab = subtract_vectors_xy(b, a) pa = subtract_vectors_xy(a, point) pb = subtract_vectors_xy(b, point) l = cross_vectors_xy(pa, pb)[2]**2 l_ab = length_vector_sqrd_xy(ab) return l / l_ab
def normal_triangle_xy(triangle, unitized=True): """Compute the normal vector of a triangle assumed to lie in the XY plane. Parameters ---------- triangle : list of list A list of triangle point coordinates. Z-coordinates are ignored. Returns ------- list The normal vector, which is a vector perpendicular to the XY plane. Raises ------ ValueError If the triangle does not have three vertices. """ a, b, c = triangle ab = subtract_vectors_xy(b, a) ac = subtract_vectors_xy(c, a) n = cross_vectors_xy(ab, ac) if not unitized: return n lvec = length_vector_xy(n) return n[0] / lvec, n[1] / lvec, n[2] / lvec
def distance_point_line_xy(point, line): """Compute the distance between a point and a line, assuming they lie in the XY-plane. This implementation computes the orthogonal 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. Parameters ---------- point : sequence of float XY(Z) coordinates of the point. line : list, tuple Line defined by two points. Returns ------- float The distance between the point and the line. References ---------- https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line """ a, b = line ab = subtract_vectors_xy(b, a) pa = subtract_vectors_xy(a, point) pb = subtract_vectors_xy(b, point) l = fabs(cross_vectors_xy(pa, pb)[2]) l_ab = length_vector_xy(ab) return l / l_ab
def normal_triangle_xy(triangle, normalised=True): a, b, c = triangle ab = subtract_vectors_xy(b, a) ac = subtract_vectors_xy(c, a) n = cross_vectors_xy(ab, ac) if not normalised: return n lvec = length_vector_xy(n) return n[0] / lvec, n[1] / lvec, n[2] / lvec
def normal_triangle_xy(triangle, unitized=True): """Compute the normal vector of a triangle assumed to lie in the XY plane. """ a, b, c = triangle ab = subtract_vectors_xy(b, a) ac = subtract_vectors_xy(c, a) n = cross_vectors_xy(ab, ac) if not unitized: return n lvec = length_vector_xy(n) return n[0] / lvec, n[1] / lvec, n[2] / lvec
def centroid_polygon_xy(polygon): r"""Compute the centroid of the surface of a polygon projected to the XY plane. Parameters ---------- polygon : list of point A sequence of polygon point XY(Z) coordinates. The Z coordinates are ignored. Returns ------- list The XYZ coordinates of the centroid. The Z coodinate is zero. Examples -------- .. code-block:: python from compas.geometry import centroid_polygon_xy 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] ] c = centroid_polygon_xy(polygon) print(c) # [0.5, 0.5, 0.0] .. code-block:: python from compas.geometry import centroid_polygon_xy from compas.geometry import centroid_points_xy polygon = [ [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [0.5, 1.0, 0.0], [0.0, 1.0, 0.0] ] c = centroid_polygon(polygon) print(c) # [0.5, 0.5, 0.0] c = centroid_points(polygon) print(c) # [0.5, 0.6, 0.0] 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 = 0 Warning ------- The polygon need not be convex. The polygon may be self-intersecting. However, it is unclear what the meaning of the centroid is in that case. """ p = len(polygon) assert p > 2, "At least three points required" if p == 3: return centroid_points_xy(polygon) cx, cy = 0.0, 0.0 A2 = 0 o = centroid_points_xy(polygon) a = polygon[-1] b = polygon[0] oa = subtract_vectors_xy(a, o) ob = subtract_vectors_xy(b, o) n0 = cross_vectors_xy(oa, ob) x, y, z = centroid_points_xy([o, a, b]) a2 = fabs(n0[2]) A2 += a2 cx += a2 * x cy += a2 * y for i in range(1, len(polygon)): a = b b = polygon[i] oa = ob ob = subtract_vectors_xy(b, o) n = cross_vectors_xy(oa, ob) x, y, z = centroid_points_xy([o, a, b]) if n[2] * n0[2] > 0: a2 = fabs(n[2]) else: a2 = -fabs(n[2]) A2 += a2 cx += a2 * x cy += a2 * y return [cx / A2, cy / A2, 0.0]
def cross(o, a, b): u = subtract_vectors(a, o) v = subtract_vectors(b, o) return cross_vectors_xy(u, v)[2]