Skip to content

Yocto/Shape: Shape utilities

Yocto/Shape is a collection of utilities for manipulating shapes in 3D graphics, with a focus on triangle and quad meshes. Yocto/Shape is implemented in yocto_shape.h and yocto_shape.cpp.

Shape representation

Yocto/Shape supports shapes defined as collection of either points, lines, triangles and quads. Shapes are represented as indexed meshes, with arbitrary properties for each vertex. Each vertex property is stored as a separate array, and shape elements are stored as arrays of indices to faces. For element parametrization, we follow Yocto/Geometry.

Shapes are represented as a simple struct called shape_data, that stores the shape elements and the vertex properties as individual arrays. Shapes can contain only one type of element, either points, lines, triangles or quads. Vertex properties are defined as separate arrays and include positions, normals, texture coords, colors, radius and tangent spaces. Vertex data is stored as vector<vecXf>, while element indices are stored as vector<vec3i>, vector<vec4i>, vector<vec2i>, vector<int> for triangle meshes, quad meshes, line sets and point sets respectively.

For shapes, you should set the shape elements, i.e. point, limes, triangles or quads, and the vertex properties, i.e. positions, normals, texture coordinates, colors and radia. Shapes support only one element type.

auto shape = shape_data{};             // create a shape
shape.triangles = vector<vec3i>{...};  // set triangle indices
shape.positions = vector<vec3f>{...};  // set positions
shape.normals = vector<vec3f>{...};    // set normals
shape.texcoords = vector<vec2f>{...};  // set texture coordinates

Additionally, Yocto/Scene supports face-varying shape, as fvshape_data, where each vertex data has its own topology. For now, only face-varying quad meshes are supported since fave-varying shapes are used mostly with Catmull-Clark subdivision.

auto shape = fvshape_data{};                // create a shape
shape.quadspos = vector<vec4i>{...};        // set face-varying indices
shape.quadstexcoord = vector<vec4i>{...};   // for positions and textures
shape.positions = vector<vec3f>{...};       // set positions
shape.texcoords = vector<vec2f>{...};       // set texture coordinates

Conversion to and from face-varying shapes is handled by the shape_to_fvshape(shape) and fvshape_to_shape(fvshape) functions. Note that conversion to an indexed shape is lossy since topology information is lost and vertices may be duplicated.

Shape serialization

Shape loading and saving is defined in Yocto/ShapeIO.

Shape properties

Several functions are defined to evaluate the geometric properties of points of shapes, indicated by the shape element id and, when needed, the shape element barycentric coordinates. Use eval_position(...) to evaluate the point position, eval_normal(...) to evaluate the interpolate point normal, eval_texcoord(...) to evaluate the point texture coordinates, eval_element_normal(...) to evaluate the point geometric normal, and eval_color(...) to evaluate the interpolate point color.

auto eid = 0; auto euv = vec3f{0.5,0.5};    // element id and uvs
auto pos  = eval_position(shape, eid, euv); // eval point position
auto norm = eval_normal(shape, eid, euv);   // eval point normal
auto st   = eval_texcoord(shape, eid, euv); // eval point texture coords
auto col  = eval_color(shape, eid, euv);    // eval point color
auto gn   = eval_element_normal(shape, eid, euv); // eval geometric normal

For shapes, we also support the computation of smooth vertex normals with compute_normals(shape) and converting to and from face-varying representations with shape_to_fvshape(shape) and fvshape_to_shape(fvshape).

Shape sampling

Shape support random sampling with a uniform distribution using sample_shape(...) and sample_shape_cdf(shape). Sampling works for lines and triangles in all cases, while for quad it requires that the elements are rectangular.

auto cdf = sample_shape_cdfd(shape);         // compute the shape CDF
auto points = sample_shape(shape, cdf, num); // sample many points
auto point = sample_shape(shape, cdf,        // sample a single point
  rand1f(rng), rand2f(rng));

Procedural shapes

Yocto/Scene has convenience function to create various procedural shapes, both for testing and for use in shape creation. These are wrappers to the corresponding functions in Yocto/Shape, where we maintain a comprehensive list of all procedural shapes supported.

Procedural shapes take as input the desired shape resolution, the shape scale, the uv scale, and additional parameters specific to that procedural shape. These functions return a quad mesh, stored as a shape_data struct. Use make_rect(...) for a rectangle in the XY plane, make_bulged_rect(...) for a bulged rectangle, make_recty(...) for a rectangle in the XZ plane, make_bulged_recty(...) for a bulged rectangle in the XZ plane, make_box(...) for a box, make_rounded_box(...) for a rounded box, make_floor(...) for a floor in the XZ plane, make_bent_floor(...) for a bent floor, make_sphere(...) for a sphere obtained from a cube, make_uvsphere(...) for a sphere tessellated along its uvs, make_capped_uvsphere(...) for a sphere with flipped caps, make_disk(...) for a disk obtained from a quad, make_bulged_disk(...) for a bulged disk, make_uvdisk(...) for a disk tessellated along its uvs, make_uvcylinder(...) for a cylinder tessellated along its uvs, make_rounded_uvcylinder(...) for a rounded cylinder.

// make shapes with 32 steps in resolution and scale of 1
auto shape_01 = make_rect({32,32}, {1,1});
auto shape_02 = make_bulged_rect({32,32}, {1,1});
auto shape_03 = make_recty({32,32}, {1,1});
auto shape_04 = make_box({32,32,32}, {1,1,1});
auto shape_05 = make_rounded_box({32,32,32}, {1,1,1});
auto shape_06 = make_floor({32,32}, {10,10});
auto shape_07 = make_bent_floor({32,32}, {10,10});
auto shape_08 = make_sphere(32, 1);
auto shape_09 = make_uvsphere({32,32}, 1);
auto shape_10 = make_capped_uvsphere({32,32}, 1);
auto shape_11 = make_disk(32, 1);
auto shape_12 = make_bulged_disk(32, 1);
auto shape_13 = make_uvdiskm({32,32}, 1);
auto shape_14 = make_uvcylinder({32,32,32}, {1,1});
auto shape_15 = make_rounded_uvcylinder({32,32,32}, {1,1});

Yocto/Shape defines a few procedural face-varying shapes with similar interfaces to the above functions. In this case, the functions return face-varying quads packed in a fvshape_data struct. Use make_fvrect(...) for a rectangle in the XY plane, make_fvbox(...) for a box, make_fvsphere(...) for a sphere obtained from a cube.

// make face-varying shapes with 32 steps in resolution and scale of 1
auto fvshape_01 = make_fvrect({32,32}, {1,1});
auto fvshape_02 = make_fvbox({32,32,32}, {1,1,1});
auto fvshape_03 = make_fvsphere(32, 1);

Yocto/Shape provides functions to create predefined shapes helpful in testing. These functions take only a scale and often provide only the positions as vertex data. These functions return either triangles, quads, or face-varying quads in a shape_data or fvshape_data struct. Use make_monkey(...) for the Blender monkey as quads and positions only, make_quad(...) for a simple quad, make_quady(...) for a simple quad in the XZ plane, make_cube(...) for a simple cube as quads and positions only, make_fvcube(...) for a simple face-varying unit cube, make_geosphere(...) for a geodesic sphere as triangles and positions only. These functions return a shape_data or fvshape_data.

auto monkey = make_monkey(1);
auto quad   = make_quad(1);
auto quady  = make_quady(1);
auto cube   = make_cube(1);
auto geosph = make_geosphere(1);
auto fvcube = make_fvcube(1);

Yocto/Shape supports the generation of points and lines sets. Use make_lines(...) to create a line set in the XY plane, make_points(...) for a collection of points at the origin, adn make_random_points(...) for a point set randomly placed in a box. These functions return points or lines, packed in a shape_data struct.

auto lines_01 = make_lines({4, 65536},      // line steps and number of lines
                           {1, 1}, {1, 1},  // line set scale and uvscale
                           {0.001, 0.001}); // radius at the bottom and top
// procedural points return points, positions, normals, texcoords, radia
auto [points, positions, normals, texcoords, radius] = make_points(65536);
auto points_01 = make_points(65536,        // number of points
                             1,            // uvscale
                             0.001);       // point radius
auto points_02 = make_random_points(65536, // number of points
                             {1, 1, 1}, 1, // line set scale and uvscale
                             0.001);       // point radius

Yocto/Shape also defines a simple functions to generate randomized hairs on a triangle or quad mesh. Use make_hair(...) to create a hair shape from a triangle and quad mesh, and return a line set.

// Make a hair ball around a shape
auto lines =  make_hair(
  make_sphere(),  // sampled surface
  {8, 65536},     // steps: line steps and number of lines
  {0.1, 0.1},     // length: minimum and maximum length
  {0.001, 0.001}, // radius: minimum and maximum radius from base to tip
  {0, 10},        // noise: noise added to hair (strength/scale)
  {0, 128},       // clump: clump added to hair (strength/number)
  {0, 0});        // rotation: rotation added to hair (angle/strength)

Finally, Yocto/Shape defines a function to create a quad mesh from a heighfield. Use make_heightfield(...) to create a heightfield meshes.

auto heightfield = vctor<float>{...};             // heightfield data
auto shape = make_heightfield(size, heightfield); // make heightfield mesh

Low-level interface: Shape representation

Yocto/Shape also support an interface where single arrays are passed as opposed to shape structs. This functionality will slowly be phased out and moved to the higher level interface in future releases. Here we give example of the low-level interface.

auto triangles = vector<vec3i>{...};   // triangle indices
auto positions = vector<vec3f>{...};   // vertex positions
auto texcoords = vector<vec2f>{...};   // vertex uvs

Face-varying shapes are also supported by specifying separate element indices for each vertex property, with arrays of vertex properties possibly of different length. This makes sure that any topology can be represented. For now, only face-varying quads are supported.

auto quadspos = vector<vec4i>{...};        // quads indices for positions
auto positions = vector<vec3f>{...};       // vertex positions
auto quadstexcoords = vector<vec4i>{...};  // quads indices for uvs
auto texcoords = vector<vec2f>{...};       // vertex uvs

Low-level interface: Vertex properties

Yocto/Shape provides many facilities to compute vertex properties for indexed elements. Use triangles_normals(...) and quads_normals(...) to compute vertex normals for triangle and quad meshes, and line_tangents(...) for line tangents. Use skin_vertices(...) to apply linear-blend skinning. Use triangle_tangent_spaces(...) to compute tangents spaces for each ech meshes.

auto triangles = vector<vec3i>{...};   // triangle indices
auto positions = vector<vec3f>{...};   // vertex positions
auto texcoords = vector<vec2f>{...};   // vertex uvs

auto normals = triangle_normals(triangles,positions);   // vertex normals
auto tangsp = triangle_tangent_spaces(triangles, positions, normals, texcoords);

auto weights = vector<vec4f>{...};   // skinning weights for 4 bones per vertex
auto joints  = vector<vec4i>{...};   // bine indices for 4 bones per vertex
auto frames  = vector<frame3f>{...}; // bone frames
auto [skinned_pos, skinned_norm] = skin_vertices(positions, normals,
   weights, joints, frames);       // skinned positions ans normals

Low-level interface: Flipping and aligning

Yocto/Shape provides functions to correct shapes that have inconsistent orientations or normals. Use flip_normals(normals) to flip all mesh normals. Use flip_triangles(triangles) and flip_quads(quads) to change face orientations. Use align_vertices(positions,alignment) to align vertex positions to the main axes.

auto triangles = vector<vec3i>{...};   // triangle indices
auto positions = vector<vec3f>{...};   // vertex positions
auto normals   = vector<vec3f>{...};   // vertex normals

triangles = flip_triangles(triangles); // flip faces
normals = flip_normals(normals);       // flip normals

// align positions to the origin along the y axis only
positions = align_vertices(positions, {0,1,0});

Low-level interface: Edges and adjacencies

Use get_edges(triangles) amd get_edges(quads) to get a list of unique edges for a triangle or quads mesh.

auto triangles = vector<vec3i>{...};  // triangle indices
auto edges = get_edges(triangles);    // edge indices

Internally, these functions use an edge_map, that is a dictionary that has pairs of vertex ids as keys and an edge index as value. Two opposing half-edges have the same representation in an edge_map, making it useful in tesselation algorithms to avoid cracks. In Yocto/Shape, edge maps also stores the number of incident faces per edge, so that we can determine which edges belong to the boundary.

auto triangles = vector<vec3i>{...};  // triangle indices
auto emap = make_edge_map(triangles); // edge map
auto edges = get_edges(emap);         // edge indices
for(auto& edge : edges)               // iterate over edges
   print(edge_index(emap, edge));     // get edge indices
auto boundary = get_boundary(emap);   // get unsorted boundary edges

Low-level interface: Ray-intersection and point-overlap

Yocto/Shape provides ray-scene intersection for points, lines, triangles and quads accelerated by a BVH data structure. Our BVH is written for minimal code and not maximum speed, but still gives fast-enough results. See Yocto/Geometry for intersection parametrization, and Yocto/Bvh for a more comprehensive version.

The BVH tree is stored in a shape_bvh struct. The tree stored an array of nodes and an array of element indices. Each node in the tree has references to either other nodes or elements. References are represented as indices in the nodes or elements arrays. Nodes indices refer to the nodes array, for internal nodes, or the element arrays, for leaf nodes. The BVH does not store shape data, which is instead passed explicitly to all calls.

BVH nodes contain their bounds, indices to the BVH arrays of either primitives or internal nodes, node element type, and the split axis. Leaf and internal nodes are identical, except that indices refer to primitives for leaf nodes or other nodes for internal nodes.

The BVH is initialized with make_triangles_bvh(bvh,triangles,positions) for triangles, make_quads_bvh(bvh,quads,positions) for quads, make_lines_bvh(bvh,lines,positions,radius) for lines, and make_points_bvh(bvh,points,positions,radius) for points.

auto triangles = vector<vec3i>{...};                 // mesh data
auto positions = vector<vec3f>{...};
auto bvh = make_triangles_bvh(triangles, positions); // BVH construction

Intersect and overlap functions return a bvh_intersection that bundles the intersection distance, the intersected element index and uvs, and a hit flag that signals whether an element was hit.

intersect_<element>_bvh(...) computed intersections between rays and shapes. Use intersect_triangles_bvh(bvh,triangles,positions, ray) for triangles, intersect_quads_bvh(bvh,quads,positions) for quads, intersect_lines_bvh(bvh,lines,positions,radius,ray) for lines, and intersect_points_bvh(bvh,points,positions,radius,ray) for points.

auto ray = ray3f{...};
// computes ray-triangles intersection
auto isec = intersect_triangles_bvh(bvh, triangles, positions, ray);
if(isec.hit) print_info(isec.element, isec.uv, isec.distance);
else print_info("no hit");

overlap_<element>_bvh(...) checks whether a shape overlaps a point within a given maximum distance and returns the distance, element and uv of the closest element. Use overlap_triangles_bvh(bvh, triangles, positions, ray) for triangles, overlap_quads_bvh(bvh, quads, positions) for quads, overlap_lines_bvh(bvh, lines, positions, radius, ray) for lines, and overlap_points_bvh(bvh, points, positions, radius, ray) for points.

auto pt = vec3f{...}; auto max_dist = float{...};
// comnpute point-triangles overlap
auto ovr = overlap_triangles_bvh(bvh, triangles, positions, pt, mat_dist);
if(ovr.hit) print_info(ovrl.element, ovrl.uv, ovrl.distance);
else print_info("no overlap");

If vertices have moved little, BVHs can be updated instead of fully rebuild. Use update_triangles_bvh(bvh, triangles, positions) for triangles, update_quads_bvh(bvh, quads, positions) for quads, update_lines_bvh(bvh, lines, positions, radius) for lines, and update_points_bvh(bvh, points, positions, radius) for points.

positions[...] = {...};                           // update positions
update_triangles_bvh(bvh, triangles, positions);  // update BVH

Low-level interface: Nearest neighbors

Nearest neighbors queries are computed by building a sparse hash grid defined as hash_grid. The grid is created by specifying a cell size for the underlying volumetric grid. Each cell stores the list of point indices that are present in that cell. To save memory, the grid is represented sparsely, using a dictionary, so that only cells with at least one vertex are defined.

Initialize a hash grid with make_hash_grid(positions, size). Use find_neighbors(grid, neighbors, position, max_radius) to find nearest neighbors.

auto positions = vector<vec3f>{...};               // point positions
auto grid = make_hash_grid(positions, cell_size);  // create hash grid
auto pt = vec3f{...}; auto max_dist = float{...};  // query point and dist
auto neighbors = vector<int>{};                    // neighbor buffer
find_neighbors(grid, neighbors, pt, max_dist);     // find neighbors by pos
find_neighbors(grid, neighbors, id, max_dist);     // find neighbors by id

Low-level interface: Element conversions and grouping

Yocto/Shape support conversion between shape elements. Use quads_to_triangles(quads) to convert quads to triangles and triangles_to_quads(triangles) to convert triangles to degenerate quads. Use bezier_to_lines(lines) to convert Bézier segments to lines using three lines for each Bézier segment.

auto quads = vector<vec4i>{...};
auto triangles = quads_to_triangles(quads);  // convert quads to triangles

Face-varying meshes are stored by having different face indices for each vertex propeerty. This way, every vertex property has its own topology. Use split_facevarying(...) to convert to an indexed mesh. During conversion vertices may be duplicated since the same topology is used for all vertex properties.

auto fvquadspos = vector<vec4i>{...};      // face-varying indices
auto fvquadsnorm = vector<vec4i>{...};     // arrays have some length
auto fvquadstexcoord = vector<vec4i>{...};
auto fvpositions = vector<vec3f>{...};     // face-varying vertices
auto fvnormals = vector<vec3f>{...};       // arrays may have different lengths
auto fvtexcoords = vector<vec2f>{...};
auto [quads, positions, normals, texcoords] = // convert to indexed mesh
   split_facevarying(fvquadspos, fvquadsnorm, fvquadstexcoord,
   fvpositions, fvnormals, fvtexcoords);

Yocto/Shape supports eliminating duplicate vertices in triangle and quad meshes. All vertices within a threshold are merged in a greedy fashion, which works well when duplicated vertices are near other while other vertices are further away. Use weld_triangles(triangles, positions, threshold) to eliminate duplicated triangle vertices and weld_quads(quads, positions, threshold) to eliminate duplicated quad vertices. For lower-level algorithms, use weld_vertices(positions, threshold) to group vertices together.

auto triangles = vector<vec3i>{...};  // mesh data
auto positions = vector<vec3f>{...};
auto tolerance = 0.1f;
auto [mtriangles, mpositions] =       // remove duplicates
   weld_triangles(triangles, positions, tolerance);

Yocto/Shape supports splitting shapes that are tagged by ids. This is helpful for example when drawing meshes that have per-face materials using renders that do support one material per shape only. Use ungroup_lines(lines,ids), ungroud_triangles(triangles,ids) and ungroup_quads(quads,ids) for lines, triangles and quads respectively.

auto triangles = vector<vec3i>{...};  // tagged mesh with one id per face
auto ids       = vector<int>{...};
auto split = ungroup_triangles(triangles, ids); // returns list of meshes

Yocto/Shape supports merging shape elements. This is useful, for example, when building up shapes from parts. The merged shapes are just concatenation of the individual shape without vertex merging. Use merge_lines(...) for lines, merge_triangles(...) for triangles and merge_quads(...) for quads.

auto triangles = vector<vec3i>{...};   // initial shape
auto positions = vector<vec3f>{...};
auto normals = vector<vec3f>{...};
auto texcoords = vector<vec2f>{...};
auto mtriangles = vector<vec3i>{...};   // shape to be merged
auto mpositions = vector<vec3f>{...};
auto mnormals = vector<vec3f>{...};
auto mtexcoords = vector<vec2f>{...};
// merge mtriangles into triangles in-placee
merge_triangles(triangles, positions, normals, texcoords,
  mtriangles, mpositions, mnormals, mtexcoords);

You can also merge triangles and quads together in other to have one primitive only. Use merge_triangles_and_quads(triangles, quads, force_triangles) to merge elements in-place. The algorithms will output quads if present or triangles if not unless force_triangles is used.

Low-level interface: Shape subdivision

Yocto/Shape defines functions to subdivide shape elements linearly, in order to obtain higher shape resolution, for example before applying displacement mapping. All functions will split all shape elements, regardless of their size. This ensures that meshes have no cracks. Use subdivide_lines(lines, vert) for lines, subdivide_triangles(triangles, vert) for triangles, subdivide_quads(quads, vert) for quads, and subdivide_bezier(beziers, vert) for Bezier segments. Alternatively, shape elemens can be subdivideede multiple times, as a convenience, with subdivide_lines(lines, vert, level) for lines, subdivide_triangles(triangles, vert, level) for triangles, subdivide_quads(quads, vert, level) for quads, and subdivide_bezier(beziers, vert, level) for Bezier segments. In this subdivision, each line is split in two lines, each triangle in three triangles, each quad in four quads, and each Bezier segment in two segments. The functions apply the subdivision rules level number of times and act on a single vertex property at a time for maximum flexibility.

auto triangles = vector<vec3i>{...};   // initial shape
auto positions = vector<vec3f>{...};
// subdivide the triangle mesh recursively two times
auto [striangles, spositions] = subdivide_triangles(triangles, positions, 2);

Yocto/Shape also supports Catmull-Clark subdivision surfaces with subdivide_catmullclark(quads, vert, level, creased). In this case, Catmull-Clark subdivision rules are used to smooth the mesh after linear subdivision. The boundary can be treated as creases with creased, which is necessary when subdividing texture coordinates.

auto quads = vector<vec4i>{...};     // initial shape
auto positions = vector<vec3f>{...};
// subdivide the quad mesh recursively two times
auto [squads, spositions] = subdivide_catmullclark(quads, positions, 2, false);

auto tquads = vector<vec4i>{...};    // face-varying shape with texture coords
auto texcoords = vector<vec2f>{...};
// subdivide the triangle mesh recursively two times
auto [stquads, stexcoords] = subdivide_catmullclark(tquads, texcoords, 2, true);

Low-level interface: Shape sampling

Yocto/Shape supports sampling meshes uniformly. All sampling require to first compute the shape CDF and then use it to sample the shape. For each shape type, the sampling functions return the shape element id and the element barycentric coordinates. Use sample_lines(cdf, re, rn) to sample lines, sample_triangles(cdf, re, rn) to sample triangles, sample_quads(cdf, re, rn) to sample quads. The shape CDFs are computed using sample_lines_dcf(lines, positions), sample_triangles_dcf(triangles, positions), and sample_quads_dcf(quads, positions).

auto triangles = vector<vec3i>{...};   // initial shape
auto positions = vector<vec3f>{...};
auto cdf = sample_triangles_cdf(triangles, positions); // shape cdf
for(auto sample : range(samples)) {
   // sample the shape returning element id and uvs
   auto [triangle_id, uv] = sample_triangles(cdf, rand1f(rng), rand2f(rng));
}

For triangles and quads, Yocto/Shape defines convenience functions that generate a set of points on the shape surface. Use sample_triangles(...) and sample_quads(...) for triangles and quads respectively.

auto triangles = vector<vec3i>{...};   // initial shape
auto positions = vector<vec3f>{...};
auto normals   = vector<vec3f>{...};
auto texcoords = vector<vec2f>{...};
auto sampled_positions = vector<vec3f>{...}; // sampled points
auto sampled_normals   = vector<vec3f>{...};
auto sampled_texcoords = vector<vec2f>{...};
// sample a set of npoints on the mesh
auto npoints = 100;
sample_triangles(sampled_positions, sampled_normals, sampled_texcoords,
                 triangles, positions, normals, texcoords, npoints);

Low-level interface: Procedural shapes

Yocto/Shape defines several procedural shapes used for both testing and to quickly create shapes for procedural scenes. Procedural shapes take as input the desired shape resolution, the shape scale, the uv scale, and additional parameters specific to that procedural shape. These functions return quads indices and vertex positions, normals and texture coordinates, with arrays passed in. Use make_rect(...) for a rectangle in the XY plane, make_bulged_rect(...) for a bulged rectangle, make_recty(...) for a rectangle in the XZ plane, make_bulged_recty(...) for a bulged rectangle in the XZ plane, make_box(...) for a box, make_rounded_box(...) for a rounded box, make_floor(...) for a floor in the XZ plane, make_bent_floor(...) for a bent floor, make_sphere(...) for a sphere obtained from a cube, make_uvsphere(...) for a sphere tessellated along its uvs, make_capped_uvsphere(...) for a sphere with flipped caps, make_disk(...) for a disk obtained from a quad, make_bulged_disk(...) for a bulged disk, make_uvdisk(...) for a disk tessellated along its uvs, make_uvcylinder(...) for a cylinder tessellated along its uvs, make_rounded_uvcylinder(...) for a rounded cylinder.

// most procedural shapes return quads, positions, normals, and texcoords
auto quads = vector<vec4i>{};
auto positions = vector<vec3f>{};
auto normals = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
// make shapes with 32 steps in resolution and scale of 1
make_rect(quads, positions, normals, texcoords, {32,32}, {1,1});
make_bulged_rect(quads, positions, normals, texcoords, {32,32}, {1,1});
make_recty(quads, positions, normals, texcoords, {32,32}, {1,1});
make_box(quads, positions, normals, texcoords, {32,32,32}, {1,1,1});
make_rounded_box(quads, positions, normals, texcoords, {32,32,32}, {1,1,1});
make_floor(quads, positions, normals, texcoords, {32,32}, {10,10});
make_bent_floor(quads, positions, normals, texcoords, {32,32}, {10,10});
make_sphere(quads, positions, normals, texcoords, 32, 1);
make_uvsphere(quads, positions, normals, texcoords, {32,32}, 1);
make_capped_uvsphere(quads, positions, normals, texcoords, {32,32}, 1);
make_disk(quads, positions, normals, texcoords, 32, 1);
make_bulged_disk(quads, positions, normals, texcoords, 32, 1);
make_uvdiskm(quads, positions, normals, texcoords, {32,32}, 1);
make_uvcylinder(quads, positions, normals, texcoords, {32,32,32}, {1,1});
make_rounded_uvcylinder(quads, positions, normals, texcoords, {32,32,32}, {1,1});

Yocto/Shape defines a few procedural face-varying shapes with similar interfaces to the above functions. In this case, the functions return face indices and vertex data for positions, normals and texture coordinates packed in a quads_fvshape struct. Use make_fvrect(...) for a rectangle in the XY plane, make_fvbox(...) for a box, make_fvsphere(...) for a sphere obtained from a cube.

// procedural face-varying shapes return positions, normals, and texcoords
auto quadspos = vector<vec4i>{};
auto quadsnorm = vector<vec4i>{};
auto quadstexcoord = vector<vec4i>{};
auto positions = vector<vec3f>{};
auto normals = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
// make face-varying shapes with 32 steps in resolution and scale of 1
make_fvrect(quadspos, quadsnorm, quadstexcoord,
            positions, normals, texcoords, {32,32}, {1,1});
make_fvbox(quadspos, quadsnorm, quadstexcoord,
           positions, normals, texcoords, {32,32,32}, {1,1,1});
make_fvsphere(quadspos, quadsnorm, quadstexcoord,
              positions, normals, texcoords, 32, 1);

Yocto/Shape provides functions to create predefined shapes helpful in testing. These functions take only a scale and often provide only the positions as vertex data. These functions return either triangles, quads, or face-varying quads. Use make_monkey(...) for the Blender monkey as quads and positions only, make_quad(...) for a simple quad, make_quady(...) for a simple quad in the XZ plane, make_cube(...) for a simple cube as quads and positions only, make_fvcube(...) for a simple face-varying unit cube, make_geosphere(...) for a geodesic sphere as triangles and positions only.

// built-in shapes return elemeents, positions, normals, and texcoords
auto quads = vector<vec4i>{};
auto triangles = vector<vec3i>{};
auto quadspos = vector<vec4i>{};
auto quadsnorm = vector<vec4i>{};
auto quadstexcoord = vector<vec4i>{};
auto positions = vector<vec3f>{};
auto normals = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
// make built-in shapes with scale of 1
make_monkey(quads, positions, normals, texcoords, 1);
make_quad(quads, positions, normals, texcoords, 1);
make_quady(quads, positions, normals, texcoords, 1);
make_cube(quads, positions, normals, texcoords, 1);
make_geosphere(triangles, positions, normals, texcoords, 1);
make_fvcube(quadspos, quadsnorm, quadstexcoord,
              positions, normals, texcoords, 1);

Yocto/Shape supports the generation of points and lines sets. Use make_lines(...) to create a line set in the XY plane, make_points(...) for a collection of points at the origin, adn make_random_points(...) for a point set randomly placed in a box. These functions return shapes that are defined in terms of lines or points and return lines or points indices, and vertex positions, normals, texture coordinates and radia, packed in a lines_shape or points_shape struct.

// procedural lines return lines, positions, normals, texcoords, radia
auto lines = vector<vec2i>{};
auto positions = vector<vec3f>{};
auto normals = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
auto radius = vector<float>{};
make_lines(lines, positions, normals, texcoords, radius,
           {4, 65536},      // line steps and number of lines
           {1, 1}, {1, 1},  // line set scale and uvscale
           {0.001, 0.001}); // radius at the bottom and top
// procedural points return points, positions, normals, texcoords, radia
make_points(points, positions, normals, texcoords, radius,
            65536,        // number of points
            1,            // uvscale
            0.001);       // point radius
make_random_points(points, positions, normals, texcoords, radius,
            65536, // number of points
            {1, 1, 1}, 1, // line set scale and uvscale
            0.001);       // point radius

Yocto/Shape also defines a simple function to generate randomized hairs on a triangle or quad mesh. Use make_hair(...) to create a hair shape from a triangle and quad mesh, and return a line set.

// Make a hair ball around a shape
auto lines = vector<vec2i>{};
auto positions = vector<vec3f>{};
auto normals = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
auto radius = vector<float>{};
make_hair(lines, positions, normals, texcoords, radius,
  surface_triangles, surface_quads, // sampled surface
  surface_positions, surface_normals, surface_texcoords
  {8, 65536},     // steps: line steps and number of lines
  {0.1, 0.1},     // length: minimum and maximum length
  {0.001, 0.001}, // radius: minimum and maximum radius from base to tip
  {0, 10},        // noise: noise added to hair (strength/scale)
  {0, 128},       // clump: clump added to hair (strength/number)
  {0, 0});        // rotation: rotation added to hair (angle/strength)

Finally, Yocto/Shape defines a function to create a quad mesh from a heighfield. Use make_heightfield(...) to create a heightfield meshes.

auto quads     = vector<vec4i>{};   // shape element buffer
auto positions = vector<vec3f>{};   // vertex data buffers
auto normals   = vector<vec3f>{};
auto texcoords = vector<vec2f>{};
auto size      = vec2i{512, 512};   // heightfield size
auto heightfield = vctor<float>{...};  // heightfield data
make_heightfield(quads, positions, normals, texcoords,
   size, heightfield);               // make heightfield mesh