def barycentric_coordinates(point, triangle): """Compute the barycentric coordinates of a point wrt to a triangle. Parameters ---------- point: list Point location. triangle: (point, point, point) A triangle defined by 3 points. Returns ------- list The barycentric coordinates of the point. """ a, b, c = triangle v0 = subtract_vectors(b, a) v1 = subtract_vectors(c, a) v2 = subtract_vectors(point, a) d00 = dot_vectors(v0, v0) d01 = dot_vectors(v0, v1) d11 = dot_vectors(v1, v1) d20 = dot_vectors(v2, v0) d21 = dot_vectors(v2, v1) D = d00 * d11 - d01 * d01 v = (d11 * d20 - d01 * d21) / D w = (d00 * d21 - d01 * d20) / D u = 1.0 - v - w return u, v, w
def barycentric_coordinates(point, triangle): """Compute the barycentric coordinates of a point wrt to a triangle. Parameters ---------- point: [float, float, float] | :class:`compas.geometry.Point` Point location. triangle: [point, point, point] A triangle defined by 3 points. Returns ------- [float, float, float] The barycentric coordinates of the point. """ a, b, c = triangle v0 = subtract_vectors(b, a) v1 = subtract_vectors(c, a) v2 = subtract_vectors(point, a) d00 = dot_vectors(v0, v0) d01 = dot_vectors(v0, v1) d11 = dot_vectors(v1, v1) d20 = dot_vectors(v2, v0) d21 = dot_vectors(v2, v1) D = d00 * d11 - d01 * d01 v = (d11 * d20 - d01 * d21) / D w = (d00 * d21 - d01 * d20) / D u = 1.0 - v - w return [u, v, w]
def matrix_from_parallel_projection(point, normal, direction): """Returns an parallel projection matrix to project onto a plane defined by point, normal and direction. Parameters ---------- point : list of float Base point of the plane. normal : list of float Normal vector of the plane. direction : list of float Direction of the projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> direction = [1, 1, 1] >>> P = matrix_from_parallel_projection(point, normal, direction) """ T = identity_matrix(4) normal = normalize_vector(normal) scale = dot_vectors(direction, normal) for j in range(3): for i in range(3): T[i][j] -= direction[i] * normal[j] / scale T[0][3], T[1][3], T[2][3] = scale_vector( direction, dot_vectors(point, normal) / scale) return T
def matrix_from_parallel_projection(plane, direction): """Returns an parallel projection matrix to project onto a plane. Parameters ---------- plane : compas.geometry.Plane or (point, normal) The plane to project onto. direction : list of float Direction of the projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> plane = (point, normal) >>> direction = [1, 1, 1] >>> P = matrix_from_parallel_projection(plane, direction) """ point, normal = plane T = identity_matrix(4) normal = normalize_vector(normal) scale = dot_vectors(direction, normal) for j in range(3): for i in range(3): T[i][j] -= direction[i] * normal[j] / scale T[0][3], T[1][3], T[2][3] = scale_vector( direction, dot_vectors(point, normal) / scale) return T
def matrix_from_shear(angle, direction, point, normal): """Constructs a shear matrix by an angle along the direction vector on the shear plane (defined by point and normal). Parameters ---------- angle : float The angle in radians. direction : list of float The direction vector as list of 3 numbers. It must be orthogonal to the normal vector. point : list of float The point of the shear plane as list of 3 numbers. normal : list of float The normal of the shear plane as list of 3 numbers. Raises ------ ValueError If direction and normal are not orthogonal. Notes ----- A point P is transformed by the shear matrix into P" such that the vector P-P" is parallel to the direction vector and its extent is given by the angle of P-P'-P", where P' is the orthogonal projection of P onto the shear plane (defined by point and normal). Examples -------- >>> angle = 0.1 >>> direction = [0.1, 0.2, 0.3] >>> point = [4, 3, 1] >>> normal = cross_vectors(direction, [1, 0.3, -0.1]) >>> S = matrix_from_shear(angle, direction, point, normal) """ fabs = math.fabs normal = normalize_vector(normal) direction = normalize_vector(direction) if fabs(dot_vectors(normal, direction)) > _EPS: raise ValueError('Direction and normal vectors are not orthogonal') angle = math.tan(angle) M = identity_matrix(4) for j in range(3): for i in range(3): M[i][j] += angle * direction[i] * normal[j] M[0][3], M[1][3], M[2][3] = scale_vector( direction, -angle * dot_vectors(point, normal)) return M
def matrix_from_orthogonal_projection(point, normal): """Returns an orthogonal projection matrix to project onto a plane defined by point and normal. Parameters ---------- point : list of float Base point of the plane. normal : list of float Normal vector of the plane. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> P = matrix_from_orthogonal_projection(point, normal) """ T = identity_matrix(4) normal = normalize_vector(normal) for j in range(3): for i in range(3): T[i][j] -= normal[i] * normal[j] # outer_product T[0][3], T[1][3], T[2][3] = scale_vector(normal, dot_vectors(point, normal)) return T
def check_cell_convexity(cell): """cell: mesh / mesh3gs check the convexity of the cell check that all the other vertices lie on the same side of that face. by calculating the face normal vector and computing the dot-product for each vector from one vertex on the face to all the others The signs must be the same. """ vkeys = cell.vertices() for fkey in cell.faces(): f_normal = cell.face_normal(fkey) f_vkeys = cell.face_vertices(fkey) f_v_xyz = cell.vertex_coordinates(f_vkeys[0]) # one vertex on the face other_vkeys = [vkey for vkey in vkeys if vkey not in f_vkeys] for i, v in enumerate(other_vkeys): v_xyz = cell.vertex_coordinates(f_vkeys[v]) vec = cg.Vector.from_start_end(f_v_xyz, v_xyz) dot = dot_vectors(f_normal, vec) if i == 0: dot_0 = dot else: if dot_0 * dot < 0: return "This is not a convex cell." print("This is a convex cell.") return True
def trimesh_edge_cotangent(mesh, u, v): """Compute the cotangent of the angle opposite a halfedge of the triangle mesh. Parameters ---------- mesh : :class:`compas.datastructures.Mesh` Instance of mesh. u : int The identifier of the first vertex of the halfedge. v : int The identifier of the second vertex of the halfedge. Returns ------- float The edge cotangent. """ fkey = mesh.halfedge[u][v] cotangent = 0.0 if fkey is not None: w = mesh.face_vertex_ancestor(fkey, u) wu = mesh.edge_vector(w, u) wv = mesh.edge_vector(w, v) length = length_vector(cross_vectors(wu, wv)) if length: cotangent = dot_vectors(wu, wv) / length return cotangent
def get_force_mags(volmesh, network): """Returns a dictionary of (u,v)-magnitude pairs. Negative magnitude means compression, while positive magnitude means tension. """ uv_hf_dict = pair_uv_to_hf(network, volmesh) mags = {} for u, v in network.edges(): hfkey = uv_hf_dict[(u, v)] edge_vector = network.edge_vector(u, v) face_normal = volmesh.halfface_oriented_normal(hfkey) face_area = volmesh.halfface_oriented_area(hfkey) dot = dot_vectors(face_normal, edge_vector) if dot < 0: factor = -1 if dot > 0: factor = 1 force = face_area * factor mags[(u, v)] = force return mags
def from_plane(cls, plane): """Construct a reflection transformation that mirrors wrt the given plane. Parameters ---------- plane : [point, vector] | :class:`compas.geometry.Plane` The reflection plane. Returns ------- :class:`compas.geometry.Reflection` The reflection transformation. """ point, normal = plane normal = normalize_vector((list(normal))) matrix = identity_matrix(4) for i in range(3): for j in range(3): matrix[i][j] -= 2.0 * normal[i] * normal[j] for i in range(3): matrix[i][3] = 2 * dot_vectors(point, normal) * normal[i] R = cls() R.matrix = matrix return R
def matrix_from_orthogonal_projection(plane): """Returns an orthogonal projection matrix to project onto a plane. Parameters ---------- plane : [point, normal] | :class:`compas.geometry.Plane` The plane to project onto. Returns ------- list[list[float]] The 4x4 transformation matrix representing an orthogonal projection. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> plane = (point, normal) >>> P = matrix_from_orthogonal_projection(plane) """ point, normal = plane T = identity_matrix(4) normal = normalize_vector(normal) for j in range(3): for i in range(3): T[i][j] -= normal[i] * normal[j] # outer_product T[0][3], T[1][3], T[2][3] = scale_vector(normal, dot_vectors(point, normal)) return T
def closest_point_on_line(a, b, c): """Closest point on line. Same as projection. Parameters ---------- a: list First line point coordinates. b: list Second line point coordinates. c: list Point coordinates. Returns ------- tuple The projected point coordinates and the distance from the input point. """ ab = subtract_vectors(b, a) ac = subtract_vectors(c, a) if length_vector(ab) == 0: return a, distance_point_point(c, a) p = add_vectors( a, scale_vector(ab, dot_vectors(ab, ac) / length_vector(ab)**2)) distance = distance_point_point(c, p) return p, distance
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 trimesh_edge_cotangent(mesh, u, v): """Compute the cotangent of the angle opposite a halfedge of the triangle mesh. Parameters ---------- mesh : compas.datastructures.Mesh The triangle mesh data structure. u : int The identifier of the first vertex of the halfedge. v : int The identifier of the second vertex of the halfedge. Returns ------- float The edge cotangent. Examples -------- .. code-block:: python pass """ fkey = mesh.halfedge[u][v] cotangent = 0.0 if fkey is not None: w = mesh.face_vertex_ancestor(fkey, u) wu = mesh.edge_vector(w, u) wv = mesh.edge_vector(w, v) l = length_vector(cross_vectors(wu, wv)) if l: cotangent = dot_vectors(wu, wv) / l return cotangent
def match_paths_orientations(pts, reference_points, is_closed): """ Check if new curve has same direction as prev curve, otherwise reverse. Parameters ---------- pts: list, :class: 'compas.geometry.Point'. The list of points whose direction we are fixing. reference_points: list, :class: 'compas.geometry.Point'. [p1, p2] Two reference points. is_closed : bool, Determines if the path is closed or open """ if len(pts) > 2 and len(reference_points) > 2: v1 = normalize_vector(subtract_vectors(pts[0], pts[2])) v2 = normalize_vector( subtract_vectors(reference_points[0], reference_points[2])) else: v1 = normalize_vector(subtract_vectors(pts[0], pts[1])) v2 = normalize_vector( subtract_vectors(reference_points[0], reference_points[1])) if dot_vectors(v1, v2) < 0: if is_closed: items = deque(reversed(pts)) items.rotate(1) # bring last point again in the front pts = list(items) else: pts.reverse() return pts
def closest_point_on_segment(a, b, c): """Closest point on segment. Different from projection because an extremity is yielded if the projection is on the line but outside the segment. Parameters ---------- a: list First line point coordinates. b: list Second line point coordinates. c: list Point coordinates. Returns ------- tuple The projected point coordinates and the distance from the input point. """ p, distance = closest_point_on_line(a, b, c) ab = subtract_vectors(b, a) ap = subtract_vectors(p, a) if dot_vectors(ab, ap) < 0: return a, distance_point_point(c, a) elif length_vector(ab) < length_vector(ap): return b, distance_point_point(c, b) else: return p, distance
def mirror_vector_vector(v1, v2): """Mirrors vector about vector. Parameters ---------- v1 : [float, float, float] | :class:`compas.geometry.Vector` The vector. v2 : [float, float, float] | :class:`compas.geometry.Vector` The normalized vector as mirror axis Returns ------- [float, float, float] The mirrored vector. Notes ----- For more info, see [1]_. References ---------- .. [1] Math Stack Exchange. *How to get a reflection vector?* Available at: https://math.stackexchange.com/questions/13261/how-to-get-a-reflection-vector. """ return subtract_vectors(v1, scale_vector(v2, 2 * dot_vectors(v1, v2)))
def face_skewnesses(mesh): """Skewnesses of the mesh faces as dict face: face skewness. https://en.wikipedia.org/wiki/Types_of_mesh Parameters ---------- mesh : Mesh A mesh. Returns ------- face_aspect_ratio_dict: dict Dictionary of aspect ratios per face {face: face aspect ratio}. Raises ------ - """ face_skewness_dict = {} for fkey in mesh.faces(): equi_angle = 180 * (1 - 2 / float(len(mesh.face_vertices(fkey)))) angles = [] face_vertices = mesh.face_vertices(fkey) for i in range(len(face_vertices)): u = mesh.vertex_coordinates(face_vertices[i - 2]) v = mesh.vertex_coordinates(face_vertices[i - 1]) w = mesh.vertex_coordinates(face_vertices[i]) uv = subtract_vectors(v, u) vw = subtract_vectors(w, v) dot = dot_vectors(uv, vw) / (length_vector(uv) * length_vector(vw)) cross = dot_vectors(mesh.face_normal(fkey), cross_vectors(uv, vw)) sign = 1 if sign < 0: sign = -1 angle = sign * degrees(acos(dot)) angles.append(angle) max_angle = maximum(angles) min_angle = minimum(angles) face_skewness_dict[fkey] = max( (max_angle - equi_angle) / (180 - equi_angle), (equi_angle - min_angle) / equi_angle) return face_skewness_dict
def get_frame(self): """ Returns a Frame with x-axis pointing up, y-axis pointing towards the mesh normal. """ if abs(dot_vectors(self.up_vector, self.mesh_normal) ) < 1.0: # if the normalized vectors are not co-linear c = cross_vectors(self.up_vector, self.mesh_normal) return Frame(self.pt, c, self.mesh_normal) else: # in horizontal surfaces the vectors happen to be co-linear return Frame(self.pt, Vector(1, 0, 0), Vector(0, 1, 0))
def trimesh_edge_cotangent(mesh, u, v): fkey = mesh.halfedge[u][v] cotangent = 0.0 if fkey is not None: w = mesh.face[fkey][v] # self.vertex_descendent(v, fkey) wu = mesh.edge_vector(w, u) wv = mesh.edge_vector(w, v) cotangent = dot_vectors(wu, wv) / length_vector(cross_vectors(wu, wv)) return cotangent
def is_negative(self, key): o = self.plane.point n = self.plane.normal if key in self.intersections: return False a = self.mesh.vertex_attributes(key, 'xyz') oa = subtract_vectors(a, o) similarity = dot_vectors(n, oa) return similarity < 0.0
def intersection_segment_plane(segment, plane, tol=1e-6): """Computes the intersection point of a line segment and a plane Parameters ---------- segment : [point, point] | :class:`compas.geometry.Line` Two points defining the line segment. plane : [point, vector] | :class:`compas.geometry.Plane` The base point and normal defining the plane. tol : float, optional A tolerance for membership verification. Returns ------- [float, float, float] | None The intersection point between the line and the plane, or None if the line and the plane are parallel. """ a, b = segment o, n = plane ab = subtract_vectors(b, a) cosa = dot_vectors(n, ab) if fabs(cosa) <= tol: # if the dot product (cosine of the angle between segment and plane) # is close to zero the line and the normal are almost perpendicular # hence there is no intersection return None # based on the ratio = -dot_vectors(n, ab) / dot_vectors(n, oa) # there are three scenarios # 1) 0.0 < ratio < 1.0: the intersection is between a and b # 2) ratio < 0.0: the intersection is on the other side of a # 3) ratio > 1.0: the intersection is on the other side of b oa = subtract_vectors(a, o) ratio = -dot_vectors(n, oa) / cosa if 0.0 <= ratio and ratio <= 1.0: ab = scale_vector(ab, ratio) return add_vectors(a, ab) return None
def matrix_from_perspective_projection(plane, center_of_projection): """Returns a perspective projection matrix to project onto a plane along lines that emanate from a single point, called the center of projection. Parameters ---------- plane : [point, normal] | :class:`compas.geometry.Plane` The plane to project onto. center_of_projection : [float, float, float] | :class:`compas.geometry.Point` The camera view point. Returns ------- list[list[float]] A 4-by-4 transformation matrix. Examples -------- >>> point = [0, 0, 0] >>> normal = [0, 0, 1] >>> plane = (point, normal) >>> center_of_projection = [1, 1, 0] >>> P = matrix_from_perspective_projection(plane, center_of_projection) """ point, normal = plane T = identity_matrix(4) normal = normalize_vector(normal) T[0][0] = T[1][1] = T[2][2] = dot_vectors( subtract_vectors(center_of_projection, point), normal) for j in range(3): for i in range(3): T[i][j] -= center_of_projection[i] * normal[j] T[0][3], T[1][3], T[2][3] = scale_vector(center_of_projection, dot_vectors(point, normal)) for i in range(3): T[3][i] -= normal[i] T[3][3] = dot_vectors(center_of_projection, normal) return T
def is_positive(self, key): o = self.plane.point n = self.plane.normal if key not in self.intersections: a = self.mesh.vertex_attributes(key, 'xyz') oa = subtract_vectors(a, o) similarity = dot_vectors(n, oa) if similarity > 0.0: return True return False
def _add_point(points, hull, p): seen_faces = [face for face in hull if _seen(points, face, p)] if len(seen_faces) == len(hull): # if can see all faces, unsee ones looking "down" normal = _normal_face(points, seen_faces[0]) seen_faces = [face for face in seen_faces if dot_vectors(_normal_face(points, face), normal) > 0] for face in seen_faces: hull.remove(face) for edge in _bdry(seen_faces): hull.append([edge[0], edge[1], p])
def dot(self, other): """The dot product of this ``Vector`` and another ``Vector``. Parameters: other (tuple, list, Vector): The vector to dot. Returns: float: The dot product. """ return dot_vectors(self, other)
def _check_deviation(volmesh, network): deviation = 0 for u, v in network.edges(): u_hf, v_hf = volmesh.cell_pair_halffaces(u, v) normal = volmesh.halfface_oriented_normal(u_hf) edge = network.edge_vector(u, v, unitized=True) dot = dot_vectors(normal, edge) perp_check = 1 - abs(dot) if perp_check > deviation: deviation = perp_check return deviation
def filter_aligned_vectors(self, vec, vecs_a, vecs_b): vec_tuples = zip(vecs_a, vecs_b) aligned_vecs = [] for vec_t in vec_tuples: if cg.dot_vectors(vec, vec_t[0]) < 0: aligned_vecs.append(vec_t[1]) else: aligned_vecs.append(vec_t[0]) return aligned_vecs
def check_for_parallel_vectors(start_beam, beams_to_check): #this function is only used once a = start_beam.frame.xaxis for beam in beams_to_check: tol = 1.0e-5 b = beam.frame.xaxis if abs(abs(dot_vectors(a, b)) - 1.0) > tol: return False return True
def __init__(self, point, normal): super(Reflection, self).__init__() normal = normalize_vector((list(normal))) for i in range(3): for j in range(3): self.matrix[i][j] -= 2.0 * normal[i] * normal[j] for i in range(3): self.matrix[i][3] = 2 * dot_vectors(point, normal) *\ normal[i]