Source code for fury.primitive

"""Module dedicated for basic primitive."""

import math
from os.path import join as pjoin

import numpy as np
from packaging.version import parse
from scipy.spatial import ConvexHull
from scipy.version import short_version

from fury.data import DATA_DIR
from fury.decorators import warn_on_args_to_kwargs
from fury.transform import cart2sphere, sphere2cart
from fury.utils import fix_winding_order

SCIPY_1_4_PLUS = parse(short_version) >= parse("1.4.0")

SPHERE_FILES = {
    "symmetric362": pjoin(DATA_DIR, "evenly_distributed_sphere_362.npz"),
    "symmetric642": pjoin(DATA_DIR, "evenly_distributed_sphere_642.npz"),
    "symmetric724": pjoin(DATA_DIR, "evenly_distributed_sphere_724.npz"),
    "repulsion724": pjoin(DATA_DIR, "repulsion724.npz"),
    "repulsion100": pjoin(DATA_DIR, "repulsion100.npz"),
    "repulsion200": pjoin(DATA_DIR, "repulsion200.npz"),
}


[docs] def faces_from_sphere_vertices(vertices): """Triangulate a set of vertices on the sphere. Parameters ---------- vertices : (M, 3) ndarray XYZ coordinates of vertices on the sphere. Returns ------- faces : (N, 3) ndarray Indices into vertices; forms triangular faces. """ hull = ConvexHull(vertices, qhull_options="Qbb Qc") faces = np.ascontiguousarray(hull.simplices) if len(vertices) < 2**16: return np.asarray(faces, np.uint16) else: return faces
[docs] @warn_on_args_to_kwargs() def repeat_primitive_function( func, centers, *, func_args=None, directions=(1, 0, 0), colors=(1, 0, 0), scales=1 ): """Repeat Vertices and triangles of a specific primitive function. It could be seen as a glyph. The primitive function should generate and return vertices and faces Parameters ---------- func : callable primitive functions centers : ndarray, shape (N, 3) Superquadrics positions func_args : args primitive functions arguments/parameters directions : ndarray, shape (N, 3) or tuple (3,), optional The orientation vector of the cone. colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,) RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1] scales : ndarray, shape (N) or (N,3) or float or int, optional The height of the cone. Returns ------- big_vertices: ndarray Expanded vertices at the centers positions big_triangles: ndarray Expanded triangles that composed our shape to duplicate big_colors : ndarray Expanded colors applied to all vertices/faces """ if func_args is None: func_args = [] # Get faces _, faces = func() if len(func_args) == 1: func_args = np.squeeze(np.array([func_args] * centers.shape[0])) elif len(func_args) != centers.shape[0]: raise IOError( "sq_params should 1 or equal to the numbers \ of centers" ) vertices = np.concatenate([func(i)[0] for i in func_args]) return repeat_primitive( vertices, faces, centers, directions=directions, colors=colors, scales=scales, have_tiled_verts=True, )
[docs] @warn_on_args_to_kwargs() def repeat_primitive( vertices, faces, centers, *, directions=None, colors=(1, 0, 0), scales=1, have_tiled_verts=False, ): """Repeat Vertices and triangles of a specific primitive shape. It could be seen as a glyph. Parameters ---------- vertices: ndarray vertices coords to duplicate at the centers positions triangles: ndarray triangles that composed our shape to duplicate centers : ndarray, shape (N, 3) Superquadrics positions directions : ndarray, shape (N, 3) or tuple (3,), optional The orientation vector of the cone. colors : ndarray (N,3) or (N, 4) or tuple (3,) or tuple (4,) RGB or RGBA (for opacity) R, G, B and A should be at the range [0, 1] scales : ndarray, shape (N) or (N,3) or float or int, optional The height of the cone. have_tiled_verts : bool option to control if we need to duplicate vertices of a shape or not Returns ------- big_vertices: ndarray Expanded vertices at the centers positions big_triangles: ndarray Expanded triangles that composed our shape to duplicate big_colors : ndarray Expanded colors applied to all vertices/faces big_centers : ndarray Expanded centers for all vertices/faces """ # duplicated vertices if needed if not have_tiled_verts: vertices = np.tile(vertices, (centers.shape[0], 1)) big_vertices = vertices # Get unit shape unit_verts_size = vertices.shape[0] // centers.shape[0] unit_triangles_size = faces.shape[0] # scale them if not isinstance(scales, np.ndarray): scales = np.array(scales) if scales.ndim == 1: if scales.size == centers.shape[0]: scales = np.repeat(scales, unit_verts_size, axis=0) scales = scales.reshape((big_vertices.shape[0], 1)) elif scales.ndim == 2: scales = np.repeat(scales, unit_verts_size, axis=0) big_vertices *= scales # update triangles big_triangles = np.array(np.tile(faces, (centers.shape[0], 1)), dtype=np.int32) big_triangles += np.repeat( np.arange(0, centers.shape[0] * unit_verts_size, step=unit_verts_size), unit_triangles_size, axis=0, ).reshape((big_triangles.shape[0], 1)) @warn_on_args_to_kwargs() def normalize_input(arr, *, arr_name=""): if ( isinstance(arr, (tuple, list, np.ndarray)) and len(arr) in [3, 4] and not all(isinstance(i, (list, tuple, np.ndarray)) for i in arr) ): return np.array([arr] * centers.shape[0]) elif isinstance(arr, np.ndarray) and len(arr) == 1: return np.repeat(arr, centers.shape[0], axis=0) elif arr is None: return np.array([]) elif len(arr) != len(centers): msg = "{} size should be 1 or ".format(arr_name) msg += "equal to the numbers of centers" raise IOError(msg) else: return np.array(arr) # update colors colors = normalize_input(colors, arr_name="colors") big_colors = np.repeat(colors, unit_verts_size, axis=0) big_colors *= 255 # update orientations directions = normalize_input(directions, arr_name="directions") for pts, dirs in enumerate(directions): # Normal vector of the object. dir_abs = np.linalg.norm(dirs) if dir_abs: normal = np.array([1.0, 0.0, 0.0]) dirs = dirs / dir_abs v = np.cross(normal, dirs) c = np.dot(normal, dirs) v1, v2, v3 = v Vmat = np.array([[0, -v3, v2], [v3, 0, -v1], [-v2, v1, 0]]) if c == -1.0: rotation_matrix = -np.eye(3, dtype=np.float64) else: h = 1 / (1 + c) rotation_matrix = ( np.eye(3, dtype=np.float64) + Vmat + (Vmat.dot(Vmat) * h) ) else: rotation_matrix = np.identity(3) big_vertices[pts * unit_verts_size : (pts + 1) * unit_verts_size] = np.dot( rotation_matrix[:3, :3], big_vertices[pts * unit_verts_size : (pts + 1) * unit_verts_size].T, ).T # apply centers position big_centers = np.repeat(centers, unit_verts_size, axis=0) big_vertices += big_centers return big_vertices, big_triangles, big_colors, big_centers
[docs] def prim_square(): """Return vertices and triangles for a square geometry. Returns ------- vertices: ndarray 4 vertices coords that composed our square triangles: ndarray 2 triangles that composed our square """ vertices = np.array( [[-0.5, -0.5, 0.0], [-0.5, 0.5, 0.0], [0.5, 0.5, 0.0], [0.5, -0.5, 0.0]] ) triangles = np.array([[0, 1, 2], [2, 3, 0]], dtype="i8") return vertices, triangles
[docs] def prim_box(): """Return vertices and triangle for a box geometry. Returns ------- vertices: ndarray 8 vertices coords that composed our box triangles: ndarray 12 triangles that composed our box """ vertices = np.array( [ [-0.5, -0.5, -0.5], [-0.5, -0.5, 0.5], [-0.5, 0.5, -0.5], [-0.5, 0.5, 0.5], [0.5, -0.5, -0.5], [0.5, -0.5, 0.5], [0.5, 0.5, -0.5], [0.5, 0.5, 0.5], ] ) triangles = np.array( [ [0, 6, 4], [0, 2, 6], [0, 3, 2], [0, 1, 3], [2, 7, 6], [2, 3, 7], [4, 6, 7], [4, 7, 5], [0, 4, 5], [0, 5, 1], [1, 5, 7], [1, 7, 3], ], dtype="i8", ) return vertices, triangles
[docs] @warn_on_args_to_kwargs() def prim_sphere(*, name="symmetric362", gen_faces=False, phi=None, theta=None): """Provide vertices and triangles of the spheres. Parameters ---------- name : str, optional which sphere - one of: * 'symmetric362' * 'symmetric642' * 'symmetric724' * 'repulsion724' * 'repulsion100' * 'repulsion200' gen_faces : bool, optional If True, triangulate a set of vertices on the sphere to get the faces. Otherwise, we load the saved faces from a file. Default: False phi : int, optional Set the number of points in the latitude direction theta : int, optional Set the number of points in the longitude direction Returns ------- vertices: ndarray vertices coords that composed our sphere triangles: ndarray triangles that composed our sphere Examples -------- >>> import numpy as np >>> from fury.primitive import prim_sphere >>> verts, faces = prim_sphere('symmetric362') >>> verts.shape == (362, 3) True >>> faces.shape == (720, 3) True """ if phi is None or theta is None: fname = SPHERE_FILES.get(name) if fname is None: raise ValueError('No sphere called "%s"' % name) res = np.load(fname) verts = res["vertices"].copy() faces = faces_from_sphere_vertices(verts) if gen_faces else res["faces"] faces = fix_winding_order(res["vertices"], faces, clockwise=True) return verts, faces else: phi = phi if phi >= 3 else 3 theta = theta if theta >= 3 else 3 phi_indices, theta_indices = np.arange(0, phi), np.arange(1, theta - 1) # phi and theta angles are same as standard physics convention phi_angles = 2 * np.pi * phi_indices / phi theta_angles = np.pi * theta_indices / (theta - 1) # combinations of all phi and theta angles mesh = np.array(np.meshgrid(phi_angles, theta_angles)) combs = mesh.T.reshape(-1, 2) _angles = np.array([[1, 1], [0, np.pi], [np.pi / 2, -np.pi / 2]]) _points = np.array(sphere2cart(_angles[0], _angles[1], _angles[2])).T x, y, z = sphere2cart(1, combs[:, 1:], combs[:, :1]) x = np.reshape(np.append(x, _points[:, :1]), (-1,)) y = np.reshape(np.append(y, _points[:, 1:2]), (-1,)) z = np.reshape(np.append(z, _points[:, -1:]), (-1,)) verts = np.vstack([x, y, z]).T faces = faces_from_sphere_vertices(verts) faces = fix_winding_order(verts, faces, clockwise=True) return verts, faces
[docs] def prim_superquadric(roundness=(1, 1), sphere_name="symmetric362"): """Provide vertices and triangles of a superquadrics. Parameters ---------- roundness : tuple, optional parameters (Phi and Theta) that control the shape of the superquadric sphere_name : str, optional which sphere - one of: * 'symmetric362' * 'symmetric642' * 'symmetric724' * 'repulsion724' * 'repulsion100' * 'repulsion200' Returns ------- vertices: ndarray vertices coords that composed our sphere triangles: ndarray triangles that composed our sphere Examples -------- >>> import numpy as np >>> from fury.primitive import prim_superquadric >>> verts, faces = prim_superquadric(roundness=(1, 1)) >>> verts.shape == (362, 3) True >>> faces.shape == (720, 3) True """ def _fexp(x, p): """Return a different kind of exponentiation.""" return np.sign(x) * (np.abs(x) ** p) sphere_verts, sphere_triangles = prim_sphere(name=sphere_name) _, sphere_phi, sphere_theta = cart2sphere(*sphere_verts.T) phi, theta = roundness x = _fexp(np.sin(sphere_phi), phi) * _fexp(np.cos(sphere_theta), theta) y = _fexp(np.sin(sphere_phi), phi) * _fexp(np.sin(sphere_theta), theta) z = _fexp(np.cos(sphere_phi), phi) xyz = np.vstack([x, y, z]).T vertices = np.ascontiguousarray(xyz) return vertices, sphere_triangles
[docs] def prim_tetrahedron(): """Return vertices and triangles for a tetrahedron. This shape has a side length of two units. Returns ------- pyramid_vert: numpy.ndarray 4 vertices coordinates triangles: numpy.ndarray 4 triangles representing the tetrahedron """ pyramid_vert = np.array( [[0.5, 0.5, 0.5], [0.5, -0.5, -0.5], [-0.5, 0.5, -0.5], [-0.5, -0.5, 0.5]] ) pyramid_triag = np.array([[2, 0, 1], [0, 2, 3], [0, 3, 1], [1, 3, 2]], dtype="i8") return pyramid_vert, pyramid_triag
[docs] def prim_icosahedron(): """Return vertices and triangles for icosahedron. Returns ------- icosahedron_vertices: numpy.ndarray 12 vertices coordinates to the icosahedron icosahedron_mesh: numpy.ndarray 20 triangles representing the tetrahedron """ phi = (1 + math.sqrt(5)) / 2.0 icosahedron_vertices = np.array( [ [-1.0, 0.0, phi], [0.0, phi, 1.0], [1.0, 0.0, phi], [-phi, 1.0, 0.0], [0.0, phi, -1.0], [phi, 1.0, 0.0], [-phi, -1.0, 0.0], [0.0, -phi, 1.0], [phi, -1.0, 0.0], [-1.0, 0.0, -phi], [0.0, -phi, -1.0], [1.0, 0.0, -phi], ] ) icosahedron_mesh = np.array( [ [1, 0, 2], [2, 5, 1], [5, 4, 1], [3, 1, 4], [0, 1, 3], [0, 6, 3], [9, 3, 6], [8, 2, 7], [2, 0, 7], [0, 7, 6], [5, 2, 8], [11, 5, 8], [11, 4, 5], [9, 11, 4], [4, 3, 9], [11, 10, 8], [8, 10, 7], [6, 7, 10], [10, 9, 6], [9, 10, 11], ], dtype="i8", ) return icosahedron_vertices, icosahedron_mesh
[docs] def prim_rhombicuboctahedron(): """Return vertices and triangles for rhombicuboctahedron. Returns ------- vertices: numpy.ndarray 24 vertices coordinates to the rhombicuboctahedron triangles: numpy.ndarray 44 triangles representing the rhombicuboctahedron """ phi = (math.sqrt(2) - 1) / 2.0 vertices = np.array( [ [0.5, phi, phi], [0.5, phi, -phi], [0.5, -phi, phi], [0.5, -phi, -phi], [phi, 0.5, phi], [phi, 0.5, -phi], [-phi, 0.5, phi], [-phi, 0.5, -phi], [phi, phi, 0.5], [phi, -phi, 0.5], [-phi, phi, 0.5], [-phi, -phi, 0.5], [-0.5, phi, phi], [-0.5, phi, -phi], [-0.5, -phi, phi], [-0.5, -phi, -phi], [phi, -0.5, phi], [phi, -0.5, -phi], [-phi, -0.5, phi], [-phi, -0.5, -phi], [phi, phi, -0.5], [phi, -phi, -0.5], [-phi, phi, -0.5], [-phi, -phi, -0.5], ] ) triangles = np.array( [ [0, 1, 2], [1, 3, 2], [0, 4, 5], [0, 5, 1], [6, 4, 7], [4, 5, 7], [0, 8, 4], [0, 2, 8], [2, 9, 8], [8, 9, 10], [9, 11, 10], [6, 8, 10], [6, 8, 4], [6, 10, 12], [6, 12, 7], [7, 12, 13], [10, 11, 14], [10, 14, 12], [12, 14, 15], [12, 15, 13], [2, 3, 16], [3, 17, 16], [2, 16, 9], [9, 16, 11], [11, 16, 18], [18, 16, 19], [16, 17, 19], [11, 18, 14], [14, 18, 19], [14, 19, 15], [1, 21, 3], [1, 20, 21], [3, 21, 17], [17, 21, 23], [17, 23, 19], [21, 20, 23], [23, 20, 22], [19, 23, 15], [15, 23, 13], [13, 23, 22], [13, 22, 7], [22, 7, 5], [22, 20, 5], [20, 1, 5], ], dtype="i8", ) triangles = fix_winding_order(vertices, triangles, clockwise=True) return vertices, triangles
[docs] @warn_on_args_to_kwargs() def prim_star(*, dim=2): """Return vertices and triangle for star geometry. Parameters ---------- dim: int Represents the dimension of the wanted star Returns ------- vertices: ndarray vertices coords that composed our star triangles: ndarray Triangles that composed our star """ if dim == 2: vert = np.array( [ [-2.0, -3.0, 0.0], [0.0, -2.0, 0.0], [3.0, -3.0, 0.0], [2.0, -1.0, 0.0], [3.0, 1.0, 0.0], [1.0, 1.0, 0.0], [0.0, 3.0, 0.0], [-1.0, 1.0, 0.0], [-3.0, 1.0, 0.0], [-2.0, -1.0, 0.0], ] ) triangles = np.array( [ [1, 9, 0], [1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [1, 9, 3], [3, 7, 9], [3, 5, 7], ], dtype="i8", ) if dim == 3: vert = np.array( [ [-2.0, -3.0, 0.0], [0.0, -2, 0.0], [3.0, -3.0, 0.0], [2.0, -1.0, 0.0], [3.0, 0.5, 0.0], [1.0, 0.5, 0.0], [0, 3.0, 0.0], [-1.0, 0.5, 0.0], [-3.0, 0.5, 0.0], [-2.0, -1.0, 0.0], [0.0, 0.0, 0.5], [0.0, 0.0, -0.5], ] ) triangles = np.array( [ [1, 9, 0], [1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [1, 9, 3], [3, 7, 9], [3, 5, 7], [1, 0, 10], [0, 9, 10], [10, 9, 8], [7, 8, 10], [6, 7, 10], [5, 6, 10], [5, 10, 4], [10, 3, 4], [3, 10, 2], [10, 1, 2], [1, 0, 11], [0, 9, 11], [11, 9, 8], [7, 8, 10], [6, 7, 11], [5, 6, 11], [5, 10, 4], [11, 3, 4], [3, 11, 2], [11, 1, 2], ], dtype="i8", ) return vert, triangles
[docs] def prim_triangularprism(): """Return vertices and triangle for a regular triangular prism. Returns ------- vertices: ndarray vertices coords that compose our prism triangles: ndarray triangles that compose our prism """ # Local variable to represent the square root of three rounded # to 7 decimal places three = float("{:.7f}".format(math.sqrt(3))) vertices = np.array( [ [0, -1 / three, 1 / 2], [-1 / 2, 1 / 2 / three, 1 / 2], [1 / 2, 1 / 2 / three, 1 / 2], [-1 / 2, 1 / 2 / three, -1 / 2], [1 / 2, 1 / 2 / three, -1 / 2], [0, -1 / three, -1 / 2], ] ) triangles = np.array( [ [0, 1, 2], [2, 1, 3], [2, 3, 4], [1, 0, 5], [1, 5, 3], [0, 2, 4], [0, 4, 5], [5, 4, 3], ] ) triangles = fix_winding_order(vertices, triangles, clockwise=True) return vertices, triangles
[docs] def prim_pentagonalprism(): """Return vertices and triangles for a pentagonal prism. Returns ------- vertices: ndarray vertices coords that compose our prism triangles: ndarray triangles that compose our prism """ # Local variable to represent the square root of five five = math.sqrt(5) onec = (five - 1) / 4.0 twoc = (five + 1) / 4.0 sone = (math.sqrt(10 + (2 * five))) / 4.0 stwo = (math.sqrt(10 - (2 * five))) / 4.0 vertices = np.array( [ [stwo / 2, twoc / 2, -0.5], [sone / 2, -onec / 2, -0.5], [0, -1 / 2, -0.5], [-sone / 2, -onec / 2, -0.5], [-stwo / 2, twoc / 2, -0.5], [stwo / 2, twoc / 2, 0.5], [sone / 2, -onec / 2, 0.5], [0, -1 / 2, 0.5], [-sone / 2, -onec / 2, 0.5], [-stwo / 2, twoc / 2, 0.5], ] ) triangles = np.array( [ [9, 5, 4], [4, 5, 0], [5, 6, 0], [0, 6, 1], [6, 7, 1], [1, 7, 2], [7, 8, 2], [2, 8, 3], [8, 9, 3], [3, 9, 4], [0, 1, 4], [1, 4, 3], [1, 3, 2], [5, 6, 9], [6, 8, 9], [6, 7, 8], ] ) triangles = fix_winding_order(vertices, triangles, clockwise=True) return vertices, triangles
[docs] def prim_octagonalprism(): """Return vertices and triangle for an octagonal prism. Returns ------- vertices: ndarray vertices coords that compose our prism triangles: ndarray triangles that compose our prism """ # Local variable to represent the square root of two rounded # to 7 decimal places two = float("{:.7f}".format(math.sqrt(2))) vertices = np.array( [ [-1, -(1 + two), -1], [1, -(1 + two), -1], [1, (1 + two), -1], [-1, (1 + two), -1], [-(1 + two), -1, -1], [(1 + two), -1, -1], [(1 + two), 1, -1], [-(1 + two), 1, -1], [-1, -(1 + two), 1], [1, -(1 + two), 1], [1, (1 + two), 1], [-1, (1 + two), 1], [-(1 + two), -1, 1], [(1 + two), -1, 1], [(1 + two), 1, 1], [-(1 + two), 1, 1], ] ) triangles = np.array( [ [0, 8, 9], [9, 1, 0], [5, 13, 9], [9, 1, 5], [3, 11, 10], [10, 2, 3], [2, 10, 14], [14, 6, 2], [5, 13, 14], [14, 6, 5], [7, 15, 11], [11, 3, 7], [7, 15, 12], [12, 4, 7], [0, 8, 12], [12, 4, 0], [0, 3, 4], [3, 4, 7], [0, 3, 1], [1, 2, 3], [2, 5, 6], [5, 2, 1], [8, 11, 12], [11, 12, 15], [8, 11, 9], [9, 10, 11], [10, 13, 14], [13, 10, 9], ], dtype="u8", ) vertices /= 4 triangles = fix_winding_order(vertices, triangles, clockwise=True) return vertices, triangles
[docs] def prim_frustum(): """Return vertices and triangle for a square frustum prism. Returns ------- vertices: ndarray vertices coords that compose our prism triangles: ndarray triangles that compose our prism """ vertices = np.array( [ [-0.5, -0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, 0.5], [-0.5, 0.5, 0.5], [-1, -1, -0.5], [1, -1, -0.5], [1, 1, -0.5], [-1, 1, -0.5], ] ) triangles = np.array( [ [4, 6, 5], [6, 4, 7], [0, 2, 1], [2, 0, 3], [4, 3, 0], [3, 4, 7], [7, 2, 3], [2, 7, 6], [6, 1, 2], [1, 6, 5], [5, 0, 1], [0, 5, 4], ], dtype="u8", ) vertices /= 2 triangles = fix_winding_order(vertices, triangles, clockwise=True) return vertices, triangles
[docs] @warn_on_args_to_kwargs() def prim_cylinder(*, radius=0.5, height=1, sectors=36, capped=True): """Return vertices and triangles for a cylinder. Parameters ---------- radius: float Radius of the cylinder height: float Height of the cylinder sectors: int Sectors in the cylinder capped: bool Whether the cylinder is capped at both ends or open Returns ------- vertices: ndarray vertices coords that compose our cylinder triangles: ndarray triangles that compose our cylinder """ if not isinstance(sectors, int): raise TypeError("Only integers are allowed for sectors parameter") if not sectors > 7: raise ValueError("Sectors parameter should be greater than 7") sector_step = 2 * math.pi / sectors unit_circle_vertices = [] # generate a unit circle on YZ plane for i in range(sectors + 1): sector_angle = i * sector_step unit_circle_vertices.append(0) unit_circle_vertices.append(math.cos(sector_angle)) unit_circle_vertices.append(math.sin(sector_angle)) vertices = [] # generate vertices for a cylinder for i in range(2): h = -height / 2 + i * height k = 0 for _ in range(sectors + 1): uy = unit_circle_vertices[k + 1] uz = unit_circle_vertices[k + 2] # position vector vertices.append(h) vertices.append(uy * radius) vertices.append(uz * radius) k += 3 # base and top circle vertices base_center_index = None top_center_index = None if capped: base_center_index = int(len(vertices) / 3) top_center_index = base_center_index + sectors + 1 for i in range(2): h = -height / 2 + i * height vertices.append(h) vertices.append(0) vertices.append(0) k = 0 for _ in range(sectors): uy = unit_circle_vertices[k + 1] uz = unit_circle_vertices[k + 2] # position vector vertices.append(h) vertices.append(uy * radius) vertices.append(uz * radius) k += 3 if capped: vertices = np.array(vertices).reshape(2 * (sectors + 1) + 2 * sectors + 2, 3) else: vertices = np.array(vertices).reshape(2 * (sectors + 1), 3) triangles = [] k1 = 0 k2 = sectors + 1 # triangles for the side surface for _ in range(sectors): triangles.append(k1) triangles.append(k2) triangles.append(k1 + 1) triangles.append(k2) triangles.append(k2 + 1) triangles.append(k1 + 1) k1 += 1 k2 += 1 if capped: k = base_center_index + 1 for i in range(sectors): if i < sectors - 1: triangles.append(base_center_index) triangles.append(k) triangles.append(k + 1) else: triangles.append(base_center_index) triangles.append(k) triangles.append(base_center_index + 1) k += 1 k = top_center_index + 1 for i in range(sectors): if i < sectors - 1: triangles.append(top_center_index) triangles.append(k + 1) triangles.append(k) else: triangles.append(top_center_index) triangles.append(top_center_index + 1) triangles.append(k) k += 1 if capped: triangles = np.array(triangles).reshape(4 * sectors, 3) else: triangles = np.array(triangles).reshape(2 * sectors, 3) return vertices, triangles
[docs] @warn_on_args_to_kwargs() def prim_arrow( *, height=1.0, resolution=10, tip_length=0.35, tip_radius=0.1, shaft_radius=0.03, ): """Return vertices and triangle for arrow geometry. Parameters ---------- height : float The height of the arrow (default: 1.0). resolution : int The resolution of the arrow. tip_length : float The tip size of the arrow (default: 0.35) tip_radius : float the tip radius of the arrow (default: 0.1) shaft_radius : float The shaft radius of the arrow (default: 0.03) Returns ------- vertices: ndarray vertices of the Arrow triangles: ndarray Triangles of the Arrow """ shaft_height = height - tip_length all_faces = [] shaft_outer_circle_down = [] shaft_outer_circle_up = [] tip_outer_circle = [] # calculating vertices for i in range(resolution + 1): x = math.cos((i * 2) * math.pi / resolution) y = math.sin((i * 2) * math.pi / resolution) shaft_x = x * shaft_radius shaft_y = y * shaft_radius tip_x = x * tip_radius tip_y = y * tip_radius # lower shaft circle (d) shaft_outer_circle_down.append((0.0, shaft_x, shaft_y)) # upper shaft circle (u) shaft_outer_circle_up.append((shaft_height, shaft_x, shaft_y)) # tip outer circle tip_outer_circle.append((shaft_height, tip_x, tip_y)) # center, center at shaft height, center at overall height v1, v2, v3 = (0.0, 0.0, 0.0), (shaft_height, 0.0, 0.0), (height, 0.0, 0.0) all_verts = ( [v1, v2, v3] + shaft_outer_circle_down + shaft_outer_circle_up + tip_outer_circle ) offset = len(shaft_outer_circle_down) off_1 = 3 off_2 = off_1 + offset off_3 = off_2 + offset # calculating triangles for i in range(resolution): # down circle d[i] , 0, d[i + 1] all_faces.append((i + off_1 + 1, i + off_1, 0)) # cylinder triangles 1 d[i], d[i + 1], u[i + 1] all_faces.append((i + off_2 + 1, i + off_1, i + off_1 + 1)) # cylinder triangles 2 u[i + 1], u[i], d[i] all_faces.append((i + off_1, i + off_2 + 1, i + off_2)) # tip circle u[i] , 1, d[i + 1] all_faces.append((i + off_3 + 1, i + off_3, 1)) # tip cone t[i], t[i + 1], 2 all_faces.append((2, i + off_3, i + off_3 + 1)) vertices = np.asarray(all_verts) triangles = np.asarray(all_faces, dtype=int) return vertices, triangles
[docs] @warn_on_args_to_kwargs() def prim_cone(*, radius=0.5, height=1, sectors=10): """Return vertices and triangle of a Cone. Parameters ---------- radius: float, optional Radius of the cone height: float, optional Height of the cone sectors: int, optional Sectors in the cone Returns ------- vertices: ndarray vertices coords that compose our cone triangles: ndarray triangles that compose our cone """ if sectors < 3: raise ValueError("Sectors parameter should be greater than 2") sector_angles = 2 * np.pi / sectors * np.arange(sectors) # Circle in YZ plane h = height / 2.0 x = np.full((sectors,), -h) y, z = radius * np.cos(sector_angles), radius * np.sin(sector_angles) x = np.concatenate((x, np.array([h, -h]))) y = np.concatenate((y, np.array([0, 0]))) z = np.concatenate((z, np.array([0, 0]))) vertices = np.vstack(np.array([x, y, z])).T # index of base and top centers base_center_index = int(len(vertices) - 1) top_center_index = base_center_index - 1 triangles = [] for i in range(sectors): if not i + 1 == top_center_index: triangles.append(top_center_index) triangles.append(i) triangles.append(i + 1) triangles.append(base_center_index) triangles.append(i + 1) triangles.append(i) else: triangles.append(top_center_index) triangles.append(i) triangles.append(0) triangles.append(base_center_index) triangles.append(0) triangles.append(i) triangles = np.array(triangles).reshape(-1, 3) return vertices, triangles