def angle_vectors(u, v, deg=False, tol=0.0): """Compute the smallest angle between two vectors. 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. deg : bool, optional If True, returns the angle in degrees. tol : float, optional Tolerance for the length of the vectors. Returns ------- float The smallest angle in radians (in degrees if ``deg == True``). The angle is always positive. Examples -------- >>> angle_vectors([0.0, 1.0, 0.0], [1.0, 0.0, 0.0]) 1.57079 """ L = length_vector(u) * length_vector(v) if tol and L < tol: return 0 a = dot_vectors(u, v) / L a = max(min(a, 1), -1) if deg: return degrees(acos(a)) return acos(a)
def angle_vectors(u, v, deg=False, tol=0.0): """Compute the smallest angle between two vectors. Parameters ---------- u : sequence of float XYZ components of the first vector. v : sequence of float XYZ components of the second vector. deg : boolean returns angle in degrees if True Returns ------- float The smallest angle in radians (in degrees if deg == True). The angle is always positive. Examples -------- >>> angle_vectors([0.0, 1.0, 0.0], [1.0, 0.0, 0.0]) """ L = length_vector(u) * length_vector(v) if tol and L < tol: return 0 a = dot_vectors(u, v) / L a = max(min(a, 1), -1) if deg: return degrees(acos(a)) return acos(a)
def distance_point_point(a, b): """Compute the distance bewteen a and b. Parameters ---------- a : sequence of float XYZ coordinates of point a. b : sequence of float XYZ coordinates of point b. Returns ------- float Distance bewteen a and b. Examples -------- >>> distance_point_point([0.0, 0.0, 0.0], [2.0, 0.0, 0.0]) 2.0 See Also -------- distance_point_point_xy """ ab = subtract_vectors(b, a) return length_vector(ab)
def distance_point_point(a, b): """Compute the distance bewteen a and b. Parameters ---------- a : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates of point a. b : [float, float, float] | :class:`compas.geometry.Point` XYZ coordinates of point b. Returns ------- float Distance bewteen a and b. Examples -------- >>> distance_point_point([0.0, 0.0, 0.0], [2.0, 0.0, 0.0]) 2.0 See Also -------- distance_point_point_xy """ ab = subtract_vectors(b, a) return length_vector(ab)
def normal_triangle_xy(triangle, unitized=True): """Compute the normal vector of a triangle assumed to lie in the XY plane. Parameters ---------- triangle : [point, point, point] | :class:`compas.geometry.Polygon` A list of triangle point coordinates. Z-coordinates are ignored. unitized : bool, optional If True, unitize the normal vector. Returns ------- [float, float, float] The normal vector, which is a vector perpendicular to the XY plane. Raises ------ AssertionError 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 return [0, 0, n[2] / length_vector(n)]
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 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 return [0, 0, n[2] / length_vector(n)]
def centroid_polygon_edges(polygon): """Compute the centroid of the edges 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 of the edges is the centroid of the midpoints of the edges, with each midpoint weighted by the length of the corresponding edge proportional to the total length of the boundary. """ L = 0 cx = 0 cy = 0 cz = 0 p = len(polygon) for i in range(-1, p - 1): p1 = polygon[i] p2 = polygon[i + 1] d = length_vector(subtract_vectors(p2, p1)) cx += 0.5 * d * (p1[0] + p2[0]) cy += 0.5 * d * (p1[1] + p2[1]) cz += 0.5 * d * (p1[2] + p2[2]) L += d return [cx / L, cy / L, cz / L]
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 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 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 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 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 area_triangle(triangle): """Compute the area of a triangle defined by three points. Parameters ---------- triangle : list of list XYZ coordinates of the corners of the triangle. Returns ------- float The area of the triangle. """ return 0.5 * length_vector(normal_triangle(triangle, False))
def area_triangle(triangle): """Compute the area of a triangle defined by three points. Parameters ---------- triangle : [point, point, point] | :class:`compas.geometry.Polygon` XYZ coordinates of the corners of the triangle. Returns ------- float The area of the triangle. """ return 0.5 * length_vector(normal_triangle(triangle, False))
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
n = cross_vectors(ab, ac) V += dot_vectors(a, n) return V / 6. # ============================================================================== # Main # ============================================================================== if __name__ == "__main__": from compas.geometry._core import Polyhedron cube = Polyhedron.generate(6) L = length_vector(subtract_vectors(cube.vertices[0], cube.vertices[1])) V1 = L * L * L V2 = volume_polyhedron(cube) print(V1 - V2 <= 1e-6) # plotter = Plotter(figsize=(10, 7)) # polygon = [ # [0, 0, 0], # [1.0, 0, 0], # [1.0, 1.0, 0], # [0.5, 0.0, 0], # [0, 1.0, 0] # ]
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]