Skip to content

Curve Support in Geometry Nodes

A design task created in March 2021, discussing supporting curves in addition to the existing point, mesh, and volume operations

Background

Background Currently, "curve data" in Blender actually refers to two or three different kinds of data:

  • Bezier / NURBS / Poly Path The curve spline itself, before any mesh data is generated. However, note that the segments of a curve are also generated data. The control points are the interactive data.
  • Surface This is a different object type, but it's quite similar to the other two types above, with a separation between the input data (control points and edges) and the generated surface. In general this design document won't discuss "Surface" data.
  • Mesh This data is generated by a combination of the original curve data and some values, like the bevel size and its resolution. Currently this is stored as a DispList in Blender, but to the user it is basically mesh data.

Because of this ambiguity, the most important thing to figure out is at which level to apply operations, similar to the existing "Apply on Spline" toggle on the curve modifier stack.

  • It's possible to imagine a few different "position" domains on a curve: the control points, points on the calculated spline, and points on any generated mesh data.
  • In any case, if generated data like the bevel geometry or the smoothed curve is written to, the curve must be converted to a mesh.

A conversion from curve to mesh changes what kind of operations are possible on the input data, so conversion from curve to mesh is an important step in the node tree.

  • The conversion is made simpler by the flexibility of geometry sets; a curve object can evaluate to mesh data in a nodes modifier (or theoretically even the other way around).
  • If any mesh modifiers are used before the nodes modifier on a curve object, the input geometry to the nodes modifier will only contain a mesh.

Most settings in the curve object data panel in the property editor describe how to evaluate the curve. However, generally the curve is actually evaluated somewhere inside the modifier stack-- just before the first "Generate" modifier that needs a mesh input. Generalizing evaluation to nodes brings up some issues because of the increased flexibility. Shouldn't the settings for the "Curve to Mesh" operation be closer to the operation itself? There are a few options:

  • Don't store the settings at all, instead provide them as inputs whenever they are needed for an operation on the curve. This is tempting because it feels low-level, but it might require repeating many of these settings for use in different operations.
    • The node can be added with versioning to existing curve objects, or it could be implicit if necessary.
    • This the "everything nodes" solution, visible, flexible, potentially low-level.
    • The utility of the settings in the property editor curve tab is not clear with this solution.
  • Since the options are stored in the curve, they could simply be controlled by a "Set Curve Info" node in the tree. A "Curve to Mesh" node then wouldn't have any settings, it would just use the settings already in the curve.
    • The settings in the property editor can be stored in the curve, so they are still useful.
    • Harder to control conversion settings in the node tree.
  • It may be possible to combine the two options above somehow. For example, the spline could be implicitly calculated as part of the curve, but a mesh surface like a beveled curve or a filled 2D curve could require a node.
    • The spline resolution would then be stored in the curve data, like it is currently. However, that does conflict with the resolution data stored per-spline.
    • The conversion to mesh using the settings in the property editor could happen implicitly after modifiers if there is any curve data left.

Conclusions

  • The curve geometry component control points use the existing "Point" domain instead of a new "Control Point" domain.
    • The point domain should be write-able without converting the curve to a mesh.
    • Leverage the differences between curves and meshes and actually treat the curve differently than a mesh.
  • A "Convert to Mesh" node is needed to access the evaluated data with attributes.
    • Making the evaluated spline available before conversion may be useful, but that mix of evaluated and original data might be a problem.
    • It's possible we would want this conversion to be implicit, but since you lose the ability to control how it's done, that may not be idea.
  • Curves have two domains, spline and points. The built-in attributes are:
Name Domain Type Notes
Length Spline Float Read-only
Shade Smooth Spline Boolean
Cyclic Spline Boolean
Radius Point Float
Tilt Point Float Only user-applied tilt?
Weight Point Float NURBS only, Only values > 0
Position Point Vector Read-only
Normal Point Vector Read-only, also evaluated data?
  • If there is still curve data left at the end of the modifier evaluation (including nodes modifiers), it will be implicitly converted to a mesh for display, just like how curves already work in Blender.

Node Mockups

While there are many opportunities to expand functionality when curves can be handled with nodes, but these mockups focus on exposing existing functionality.

This is mostly just like the current curve geometry settings. The bevel radius is also influenced by the radius attribute.
Curve offset becomes a separate node, and can even controlled by an attribute. This is opposed to only doing the offset when evaluating the resulting mesh.
The settings in the "Start and End Mapping" can become a separate node, because they're useful even when you don't want to generate geometry from a curve.
The drop-down can let you choose to use a distance between specifying a distance between points or a total number of sampled points on each spline.
The selection is a boolean attribute in the spline domain.
The selection is a boolean attribute in the point domain. One complication is that only bezier splines have handles.
The curve profile widget can be a quick way to input a bezier spline, at first specifically for use with the bevel modifier, or with curve bevel.
This deformation node could trivially support every geometry component type (including curves!)

Implementation Details

The current curve data structures are confusing and not very suited to be used in this context, and improving them is on the radar of the modeling module anyway. Improved C++ data structures could significantly ease the process of implementing these features.

With these data structures, a few operations would be necessary as a minimum before they are useful at all:

  • Conversion from curve DNA structs to these data structures.
  • Evaluation of the structures (calculate spline, bevel geometry), with a Mesh result.

Some of the data stored below depends on decisions made in the previous sections.

enum class BezierHandleType {
  Free,
  Auto,
  Vector,
  Align,
};

struct ControlPoint {
  float3 position;
  float radius;
  float tilt;
};

struct ControlPointBezier : ControlPoint {
  float3 handle_position_a;
  float3 handle_position_b;
  BezierHandleType handle_type_a;
  BezierHandleType handle_type_b;
};

struct ControlPointNURBS : ControlPoint {
  float weight;
};

enum class SplineType {
  Bezier,
  Poly,
  NURBS,
};

struct Spline {
  SplineType type;
};

struct SplineBezier : Spline {
  Vector<ControlPointBezier> control_points;
  int32_t flag; /* Cyclic, smooth. */
  int32_t resolution_u;
  int32_t resolution_v;
};

struct SplineNURBS : Spline {
  Vector<ControlPointNURBS> control_points;
  int32_t flag; /* Cyclic, smooth. */
  int32_t resolution_u;
  int32_t resolution_v;
  uint8_t order;
};

/* Proposed name to be different from DNA type. */
struct EditCurve {
  Vector<Spline *> splines;
  AttributeStorage attributes;
  int32_t flag; /* 2D. */

  /* Attributes. */
  CustomData *control_point_data;
  CustomData *spline_data;

  /* Then maybe whatever caches are necessary, etc. */
  Vector<float3> evaluated_spline_cache;
};