def delaunay_from_points(points, boundary=None, holes=None, tiny=1e-12): """Computes the delaunay triangulation for a list of points. Parameters ---------- points : sequence of tuple XYZ coordinates of the original points. boundary : sequence of tuples list of ordered points describing the outer boundary (optional) holes : list of sequences of tuples list of polygons (ordered points describing internal holes (optional) Returns ------- list The faces of the triangulation. Each face is a triplet of indices referring to the list of point coordinates. Notes ----- For more info, see [1]_. References ---------- .. [1] Sloan, S. W., 1987 *A fast algorithm for constructing Delaunay triangulations in the plane* Advances in Engineering Software 9(1): 34-55, 1978. Example ------- .. plot:: :include-source: from compas.datastructures import Mesh from compas.geometry import pointcloud_xy from compas.geometry import delaunay_from_points from compas_plotters import MeshPlotter points = pointcloud_xy(20, (0, 50)) faces = delaunay_from_points(points) delaunay = Mesh.from_vertices_and_faces(points, faces) plotter = MeshPlotter(delaunay) plotter.draw_vertices(radius=0.1) plotter.draw_faces() plotter.show() """ from compas.datastructures import Mesh from compas.datastructures import trimesh_swap_edge def super_triangle(coords): centpt = centroid_points(coords) bbpts = bounding_box(coords) dis = distance_point_point(bbpts[0], bbpts[2]) dis = dis * 300 v1 = (0 * dis, 2 * dis, 0) v2 = (1.73205 * dis, -1.0000000000001 * dis, 0) # due to numerical issues v3 = (-1.73205 * dis, -1 * dis, 0) pt1 = add_vectors(centpt, v1) pt2 = add_vectors(centpt, v2) pt3 = add_vectors(centpt, v3) return pt1, pt2, pt3 mesh = Mesh() # to avoid numerical issues for perfectly structured point sets points = [(point[0] + random.uniform(-tiny, tiny), point[1] + random.uniform(-tiny, tiny), 0.0) for point in points] # create super triangle pt1, pt2, pt3 = super_triangle(points) # add super triangle vertices to mesh n = len(points) super_keys = n, n + 1, n + 2 mesh.add_vertex(super_keys[0], {'x': pt1[0], 'y': pt1[1], 'z': pt1[2]}) mesh.add_vertex(super_keys[1], {'x': pt2[0], 'y': pt2[1], 'z': pt2[2]}) mesh.add_vertex(super_keys[2], {'x': pt3[0], 'y': pt3[1], 'z': pt3[2]}) mesh.add_face(super_keys) # iterate over points for i, pt in enumerate(points): key = i # newtris should be intialised here # check in which triangle this point falls for fkey in list(mesh.faces()): # abc = mesh.face_coordinates(fkey) #This is slower # This is faster: keya, keyb, keyc = mesh.face_vertices(fkey) dicta = mesh.vertex[keya] dictb = mesh.vertex[keyb] dictc = mesh.vertex[keyc] a = [dicta['x'], dicta['y']] b = [dictb['x'], dictb['y']] c = [dictc['x'], dictc['y']] if is_point_in_triangle_xy(pt, [a, b, c], True): # generate 3 new triangles (faces) and delete surrounding triangle key, newtris = mesh.insert_vertex(fkey, key=key, xyz=pt, return_fkeys=True) break while newtris: fkey = newtris.pop() # get opposite_face keys = mesh.face_vertices(fkey) s = list(set(keys) - set([key])) u, v = s[0], s[1] fkey1 = mesh.halfedge[u][v] if fkey1 != fkey: fkey_op, u, v = fkey1, u, v else: fkey_op, u, v = mesh.halfedge[v][u], u, v if fkey_op: keya, keyb, keyc = mesh.face_vertices(fkey_op) dicta = mesh.vertex[keya] a = [dicta['x'], dicta['y']] dictb = mesh.vertex[keyb] b = [dictb['x'], dictb['y']] dictc = mesh.vertex[keyc] c = [dictc['x'], dictc['y']] circle = circle_from_points_xy(a, b, c) if is_point_in_circle_xy(pt, circle): fkey, fkey_op = trimesh_swap_edge(mesh, u, v) newtris.append(fkey) newtris.append(fkey_op) # Delete faces adjacent to supertriangle for key in super_keys: mesh.delete_vertex(key) # Delete faces outside of boundary if boundary: for fkey in list(mesh.faces()): centroid = mesh.face_centroid(fkey) if not is_point_in_polygon_xy(centroid, boundary): mesh.delete_face(fkey) # Delete faces inside of inside boundaries if holes: for polygon in holes: for fkey in list(mesh.faces()): centroid = mesh.face_centroid(fkey) if is_point_in_polygon_xy(centroid, polygon): mesh.delete_face(fkey) return [mesh.face_vertices(fkey) for fkey in mesh.faces()]
def delaunay_from_points(points, boundary=None, holes=None, tiny=1e-12): """Computes the delaunay triangulation for a list of points. Parameters ---------- points : sequence[[float, float, float] | :class:`compas.geometry.Point`] XYZ coordinates of the original points. boundary : sequence[[float, float, float] | :class:`compas.geometry.Point`] | :class:`compas.geometry.Polygon`, optional List of ordered points describing the outer boundary. holes : sequence[sequence[[float, float, float] | :class:`compas.geometry.Point`] | :class:`compas.geometry.Polygon`], optional List of polygons (ordered points describing internal holes. Returns ------- list[[int, int, int]] The faces of the triangulation. Each face is a triplet of indices referring to the list of point coordinates. Notes ----- For more info, see [1]_. References ---------- .. [1] Sloan, S. W., 1987 *A fast algorithm for constructing Delaunay triangulations in the plane* Advances in Engineering Software 9(1): 34-55, 1978. Examples -------- >>> """ from compas.datastructures import Mesh from compas.datastructures import trimesh_swap_edge def super_triangle(coords, ccw=True): centpt = centroid_points(coords) bbpts = bounding_box(coords) dis = distance_point_point(bbpts[0], bbpts[2]) dis = dis * 300 v1 = (0 * dis, 2 * dis, 0) v2 = (1.73205 * dis, -1.0000000000001 * dis, 0 ) # due to numerical issues v3 = (-1.73205 * dis, -1 * dis, 0) pt1 = add_vectors(centpt, v1) pt2 = add_vectors(centpt, v2) pt3 = add_vectors(centpt, v3) if ccw: return pt1, pt3, pt2 return pt1, pt2, pt3 mesh = Mesh() # to avoid numerical issues for perfectly structured point sets points = [(point[0] + random.uniform(-tiny, tiny), point[1] + random.uniform(-tiny, tiny), 0.0) for point in points] # create super triangle pt1, pt2, pt3 = super_triangle(points) # add super triangle vertices to mesh n = len(points) super_keys = n, n + 1, n + 2 mesh.add_vertex(super_keys[0], {'x': pt1[0], 'y': pt1[1], 'z': pt1[2]}) mesh.add_vertex(super_keys[1], {'x': pt2[0], 'y': pt2[1], 'z': pt2[2]}) mesh.add_vertex(super_keys[2], {'x': pt3[0], 'y': pt3[1], 'z': pt3[2]}) mesh.add_face(super_keys) # iterate over points for key, point in enumerate(points): # newtris should be intialised here # check in which triangle this point falls for fkey in list(mesh.faces()): abc = mesh.face_coordinates(fkey) if is_point_in_triangle_xy(point, abc, True): # generate 3 new triangles (faces) and delete surrounding triangle key, newtris = mesh.insert_vertex(fkey, key=key, xyz=point, return_fkeys=True) break while newtris: fkey = newtris.pop() face = mesh.face_vertices(fkey) i = face.index(key) u = face[i - 2] v = face[i - 1] nbr = mesh.halfedge[v][u] if nbr is not None: a, b, c = mesh.face_coordinates(nbr) circle = circle_from_points_xy(a, b, c) if is_point_in_circle_xy(point, circle): fkey, nbr = trimesh_swap_edge(mesh, u, v) newtris.append(fkey) newtris.append(nbr) # Delete faces adjacent to supertriangle for key in super_keys: mesh.delete_vertex(key) # Delete faces outside of boundary if boundary: for fkey in list(mesh.faces()): centroid = mesh.face_centroid(fkey) if not is_point_in_polygon_xy(centroid, boundary): mesh.delete_face(fkey) # Delete faces inside of inside boundaries if holes: for polygon in holes: for fkey in list(mesh.faces()): centroid = mesh.face_centroid(fkey) if is_point_in_polygon_xy(centroid, polygon): mesh.delete_face(fkey) return [mesh.face_vertices(fkey) for fkey in mesh.faces()]
def voronoi_from_delaunay(delaunay): """Construct the Voronoi dual of the triangulation of a set of points. Parameters ---------- delaunay : Mesh A delaunay mesh. Returns ------- Mesh The corresponding voronoi mesh. Warning ------- This function does not work properly if all vertices of the delaunay are on the boundary. Example ------- .. plot:: :include-source: from compas.datastructures import Mesh from compas.topology import trimesh_remesh from compas.topology import delaunay_from_points from compas.topology import voronoi_from_delaunay from compas.geometry import pointcloud_xy from compas.plotters import MeshPlotter points = pointcloud_xy(10, (0, 10)) faces = delaunay_from_points(points) delaunay = Mesh.from_vertices_and_faces(points, faces) trimesh_remesh(delaunay, 1.0, allow_boundary_split=True) points = [delaunay.vertex_coordinates(key) for key in delaunay.vertices()] faces = delaunay_from_points(points) delaunay = Mesh.from_vertices_and_faces(points, faces) voronoi = voronoi_from_delaunay(delaunay) lines = [] for u, v in voronoi.edges(): lines.append({ 'start': voronoi.vertex_coordinates(u, 'xy'), 'end' : voronoi.vertex_coordinates(v, 'xy'), 'width': 1.0 }) plotter = MeshPlotter(delaunay, figsize=(10, 6)) plotter.draw_lines(lines) plotter.draw_vertices( radius=0.075, facecolor={key: '#0092d2' for key in delaunay.vertices() if key not in delaunay.vertices_on_boundary()}) plotter.draw_edges(color='#cccccc') plotter.show() """ voronoi = mesh_dual(delaunay) for key in voronoi.vertices(): a, b, c = delaunay.face_coordinates(key) center, radius, normal = circle_from_points_xy(a, b, c) voronoi.vertex[key]['x'] = center[0] voronoi.vertex[key]['y'] = center[1] voronoi.vertex[key]['z'] = center[2] return voronoi
def mesh_delaunay_from_points(points, polygon=None, polygons=None): """Computes the delaunay triangulation for a list of points. Parameters: points (sequence of tuple): XYZ coordinates of the original points. polygon (sequence of tuples): list of ordered points describing the outer boundary (optional) polygons (list of sequences of tuples): list of polygons (ordered points describing internal holes (optional) Returns: list of lists: list of faces (face = list of vertex indices as integers) References: Sloan, S. W. (1987) A fast algorithm for constructing Delaunay triangulations in the plane Example: .. plot:: :include-source: import compas from compas.datastructures.mesh import Mesh from compas.visualization.plotters import MeshPlotter from compas.datastructures.mesh.algorithms import mesh_delaunay_from_points mesh = Mesh.from_obj(compas.get_data('faces.obj')) vertices = [mesh.vertex_coordinates(key) for key in mesh] faces = mesh_delaunay_from_points(vertices) delaunay = Mesh.from_vertices_and_faces(vertices, faces) plotter = MeshPlotter(delaunay) plotter.draw_vertices(radius=0.1) plotter.draw_faces() plotter.show() """ def super_triangle(coords): centpt = centroid_points(coords) bbpts = bounding_box(coords) dis = distance_point_point(bbpts[0], bbpts[2]) dis = dis * 300 v1 = (0 * dis, 2 * dis, 0) v2 = (1.73205 * dis, -1.0000000000001 * dis, 0 ) # due to numerical issues v3 = (-1.73205 * dis, -1 * dis, 0) pt1 = add_vectors(centpt, v1) pt2 = add_vectors(centpt, v2) pt3 = add_vectors(centpt, v3) return pt1, pt2, pt3 mesh = Mesh() # to avoid numerical issues for perfectly structured point sets tiny = 1e-8 pts = [(point[0] + random.uniform(-tiny, tiny), point[1] + random.uniform(-tiny, tiny), 0.0) for point in points] # create super triangle pt1, pt2, pt3 = super_triangle(points) # add super triangle vertices to mesh n = len(points) super_keys = n, n + 1, n + 2 mesh.add_vertex(super_keys[0], {'x': pt1[0], 'y': pt1[1], 'z': pt1[2]}) mesh.add_vertex(super_keys[1], {'x': pt2[0], 'y': pt2[1], 'z': pt2[2]}) mesh.add_vertex(super_keys[2], {'x': pt3[0], 'y': pt3[1], 'z': pt3[2]}) mesh.add_face(super_keys) # iterate over points for i, pt in enumerate(pts): key = i # check in which triangle this point falls for fkey in list(mesh.faces()): # abc = mesh.face_coordinates(fkey) #This is slower # This is faster: keya, keyb, keyc = mesh.face_vertices(fkey) dicta = mesh.vertex[keya] dictb = mesh.vertex[keyb] dictc = mesh.vertex[keyc] a = [dicta['x'], dicta['y']] b = [dictb['x'], dictb['y']] c = [dictc['x'], dictc['y']] if is_point_in_triangle_xy(pt, [a, b, c]): # generate 3 new triangles (faces) and delete surrounding triangle newtris = mesh.insert_vertex(fkey, key=key, xyz=pt) break while newtris: fkey = newtris.pop() # get opposite_face keys = mesh.face_vertices(fkey) s = list(set(keys) - set([key])) u, v = s[0], s[1] fkey1 = mesh.halfedge[u][v] if fkey1 != fkey: fkey_op, u, v = fkey1, u, v else: fkey_op, u, v = mesh.halfedge[v][u], u, v if fkey_op: keya, keyb, keyc = mesh.face_vertices(fkey_op) dicta = mesh.vertex[keya] a = [dicta['x'], dicta['y']] dictb = mesh.vertex[keyb] b = [dictb['x'], dictb['y']] dictc = mesh.vertex[keyc] c = [dictc['x'], dictc['y']] circle = circle_from_points_xy(a, b, c) if is_point_in_circle_xy(pt, circle): fkey, fkey_op = trimesh_swap_edge(mesh, u, v) newtris.append(fkey) newtris.append(fkey_op) # Delete faces adjacent to supertriangle for key in super_keys: mesh.remove_vertex(key) # Delete faces outside of boundary if polygon: for fkey in list(mesh.faces()): cent = mesh.face_centroid(fkey) if not is_point_in_polygon_xy(cent, polygon): mesh.delete_face(fkey) # Delete faces inside of inside boundaries if polygons: for polygon in polygons: for fkey in list(mesh.faces()): cent = mesh.face_centroid(fkey) if is_point_in_polygon_xy(cent, polygon): mesh.delete_face(fkey) return [[int(key) for key in mesh.face_vertices(fkey, True)] for fkey in mesh.faces()]
def mesh_voronoi_from_points(points, boundary=None, holes=None, return_delaunay=False): """Construct the Voronoi dual of the triangulation of a set of points. Parameters: points boundary holes return_delaunay Example: .. plot:: :include-source: from numpy import random from numpy import hstack from numpy import zeros from compas.datastructures.mesh import Mesh from compas.visualization.plotters import MeshPlotter from compas.datastructures.mesh.algorithms import trimesh_optimise_topology from compas.datastructures.mesh.algorithms import mesh_delaunay_from_points from compas.datastructures.mesh.algorithms import mesh_voronoi_from_points points = hstack((10.0 * random.random_sample((20, 2)), zeros((20, 1)))).tolist() mesh = Mesh.from_vertices_and_faces(points, mesh_delaunay_from_points(points)) trimesh_optimise_topology(mesh, 1.0, allow_boundary_split=True) points = [mesh.vertex_coordinates(key) for key in mesh] voronoi, delaunay = mesh_voronoi_from_points(points, return_delaunay=True) lines = [] for u, v in voronoi.edges(): lines.append({ 'start': voronoi.vertex_coordinates(u, 'xy'), 'end' : voronoi.vertex_coordinates(v, 'xy') }) boundary = set(delaunay.vertices_on_boundary()) plotter = MeshPlotter(delaunay) plotter.draw_xlines(lines) plotter.draw_vertices(radius=0.075, facecolor={key: '#0092d2' for key in delaunay if key not in boundary}) plotter.draw_edges(color='#cccccc') plotter.show() """ delaunay = Mesh.from_vertices_and_faces(points, mesh_delaunay_from_points(points)) voronoi = mesh_dual(delaunay) for key in voronoi: a, b, c = delaunay.face_coordinates(key) center, radius, normal = circle_from_points_xy(a, b, c) voronoi.vertex[key]['x'] = center[0] voronoi.vertex[key]['y'] = center[1] voronoi.vertex[key]['z'] = center[2] if return_delaunay: return voronoi, delaunay return voronoi
def discretise_faces(vertices, faces, target, min_angle=15, factor=3, iterations=100, refine=True): """ Make an FE mesh from input coarse mesh data. Parameters ---------- vertices : list Co-ordinates of coarse mesh vertices. faces : list Vertex numbers of each face of the coarse mesh. target : float Target length of each triangle. min_angle : float Minimum internal angle of triangles. factor : float Factor on the maximum area of each triangle. iterations : int Number of iterations per face. refine : bool Refine beyond Delaunay. Returns ------- list Vertices of discretised faces. list Triangles of discretised faces. """ points_all = [] faces_all = [] Amax = factor * 0.5 * target**2 for count, face in enumerate(faces): print('Face {0}/{1}'.format(count + 1, len(faces))) # Seed face.append(face[0]) points = [] for u, v in zip(face[:-1], face[1:]): sp = vertices[u] ep = vertices[v] vec = subtract_vectors(ep, sp) l = length_vector(vec) n = max([1, int(l / target)]) for j in range(n): points.append(add_vectors(sp, scale_vector(vec, j / n))) # Starting orientation centroid = centroid_points(points) vec1 = subtract_vectors(points[1], points[0]) vecc = subtract_vectors(centroid, points[0]) vecn = cross_vectors(vec1, vecc) # Rotate about x points = array(points).transpose() phi = -arctan2(vecn[2], vecn[1]) + pi / 2 Rx = array([[1., 0., 0.], [0., cos(phi), -sin(phi)], [0., sin(phi), cos(phi)]]) vecn_x = dot(Rx, array(vecn)[:, newaxis]) points_x = dot(Rx, points) Rxinv = inv(Rx) # Rotate about y psi = +arctan2(vecn_x[2, 0], vecn_x[0, 0]) - pi / 2 Ry = array([[cos(psi), 0., sin(psi)], [0., 1., 0.], [-sin(psi), 0., cos(psi)]]) points_y = dot(Ry, points_x) Ryinv = inv(Ry) # Store Vs = points_y.transpose() DTs = Delaunay(Vs[:, :2], furthest_site=False, incremental=False) tris = DTs.simplices points_xs = dot(Ryinv, Vs.transpose()) points_new = [list(i) for i in list(dot(Rxinv, points_xs).transpose())] faces_new = [[int(i) for i in tri] for tri in list(tris)] # Refine if refine: V = points_y.transpose() z = float(V[0, 2]) it = 0 while it < iterations: DT = Delaunay(V[:, :2], furthest_site=False, incremental=False) tris = DT.simplices for u, v, w in tris: p1 = [float(i) for i in V[u, :2]] p2 = [float(i) for i in V[v, :2]] p3 = [float(i) for i in V[w, :2]] # th1 = angles_points_xy(p1, p2, p3)[0] * 180 / pi # th2 = angles_points_xy(p2, p3, p1)[0] * 180 / pi # th3 = angles_points_xy(p3, p1, p2)[0] * 180 / pi # print(th1, th2, th3) ] leads to some 0 and 180 # thm = min([th1, th2, th3]) res = circle_from_points_xy(p1, p2, p3) if res: c, r, _ = res c[2] = z A = area_polygon_xy([p1, p2, p3]) # if (thm < min_angle) or (A > Amax): if A > Amax: dist = distance_matrix(array([c]), V, threshold=10**5) ins = len(dist[dist <= r]) if ins <= 3: V = vstack([V, array([c])]) break else: continue it += 1 print('Iterations {0}'.format(it)) points_x = dot(Ryinv, V.transpose()) points_new = [ list(i) for i in list(dot(Rxinv, points_x).transpose()) ] faces_new = [[int(i) for i in tri] for tri in list(tris)] points_all.append(points_new) faces_all.append(faces_new) return points_all, faces_all