Skip to content

Curves Object

The curves object is intended to replace the hair particle system and the existing curve object type. Geometry sets allow flexibility of object types from mesh objects with geometry nodes.

  • T68981: New curves object type
  • T95355: New Curves data block

Data Structures

  • Object can be of type OB_CURVES, and then uses a Curves data-block as object data.
  • Curves is an ID data-block data-block used to store curves.
  • CurvesGeometry that contains an array of curves and their control points.

Curves Geometry

The CurvesGeometry struct contains curve and control point attributes, the topology of curves, and any caches relating to that data. CurvesGeometry is a separate type from Curves so it can have C++ RAII semantics and so it can be embedded in other Blender ID data-blocks in the future.

Topology is encoded with a single array of integers: each value is each curve's offset into the point array. The number of points in the curve is retrieved from an offset and the next offset. Arbitrary attributes can be created on both curves and points.

Multiple curve types are supported:

  1. Catmull Rom: Catmull Rom curves are like Bezier curves but use the four neighboring control points to describe the tangents at each point. They provide simple continuity and local control.
  2. Poly Poly curves have no interpolation at all. The main reason for this type is reduced complexity and increased performance.
  3. Bezier A Bézier curve is made up of a many curve segments, possibly achieving continuity of curvature by constraining the alignment of curve handles. In order to correspond with user expectations, "handle" control points are stored in separate handle_{position/type}_{left/right} attributes.
  4. NURBS Non-Uniform Rational B-Splines are a superset of the other types-- more advanced but more flexible. The mapping from control points to evaluated points is influenced by a vector of knots, weights for each point, and the order/degree of the curve.

These types can be mixed together in a single curves data-block, though generally code should prioritize performance when all types are the same rather than when they are mixed.


The Curves data-block contains an embedded CurvesGeometry struct and a few more high-level variables like the selection domain for editing.

Attachment & Deformation

Each curve can be attached to a mesh surface with UV coordinates stored in the surface_uv_coords attribute. The attachment information can be used to interpolate mesh attributes to curves or to find the position at the surface.

Using a UV map works well because:

  • Many mesh editing operations, even ones that change topology, keep UV maps intact.
  • UV maps are a concept that is well known to users and thus no new concept has to be learned.
  • UV maps can be generated, accessed and modified using geometry nodes.
  • UV maps exist on surface usually anyway, so there is no extra cost on propagating it for other usages.
  • Tangents for curve deformation are clearer, since they can take the UV map into account.

The main downsides to using UV maps are:

  • A UV coordinate can correspond to an arbitrary number points on a surface. UV islands shouldn’t overlap for predictable results.
  • Doing a reverse lookup from UV coordinates to positions on a mesh comes at a cost. This is implemented with ReverseUVSampler.


Cycles and EEVEE can render hair and their attributes natively as Catmull-Rom curves, for memory efficiency. However, there are still many limitations like no support for cyclic curves or other curve types. EEVEE always resamples curves to have a small number of control points.

Improving rendering support is tracked in T96455.

Hair System

The hair system is implemented with curves sculpt mode and geometry nodes.

Future Work

  • Edit mode still needs to be implemented
  • Node group operators will allow custom edit/sculpt mode tools
  • A generic simulation system will allow physics simulations
  • More geometry nodes will make it possible to build procedural hair generation node groups.

Common Patterns

Algorithms that change point count but keep the same number of curves

  1. With an IndexMask selection of curves, find the inverse selection (.complement(...))
  2. Create the new curves with no points but the same curve count: bke::curves::copy_only_curve_domain 1. This avoids duplicating any data in the curves with implicit sharing.
  3. Copy sizes of unselected curves into the new curves offsets: offset_indices::copy_group_sizes
  4. Calculate the result sizes of the selected curves, store in the new curves offsets as well 1. Depending on the complexity of the algorithm, knowing the number of points in the result curve can require doing the majority of the work. When this is unavoidable, it's best to store a minimum of information that will allow filling the point attributes after the new curves are created with the correct point count.
  5. Transform the curve sizes into offsets with offset_indices::accumulate_counts_to_offsets
  6. Create the curves with the calculated final point count (the last offset): CurvesGeometry::resize(offsets.total_size(), curves.curves_num())
  7. Copy point data for all unselected curve points with bke::gather_attributes_group_to_group
  8. Do the operation & interpolate attributes to selected curves