2.10. The Curve Node

The curve node stores a set of curve definitions, defined as cubic Bézier curves and optimized for dynamic editing. The intended use is for animation, or other circumstances when there is a need to smoothly interpolate a single parameter.

2.10.1. Curves

The content of a curve node is arranged into individual curves, with each curve having its own content and being made available for subscription as single unit. Each curve is identified by a numerical ID, just as with layers and buffers. Curves also have names, that must be unique within the same node.

A curves expresses how a variable changes over time, by specifying control points to interpolate between. The variable can be of one, two, three or four dimensions. Most examples below show the case for a one-dimensional variable, in which case the entire curve can be visualized as an ordinary flat curve. Visualizing it for more than one dimension can be done for instance by overlapping several such flat curves.

The way curves are used is by taking the value from a curve, and injecting it into some context to control some parameter. For example, curves can be used to control bone transformations, creating smooth animation that is easily controlled by editing curves rather than complex transforms directly. Or, they can be used to control aspects of materials, or the light emitted by an object.

2.10.1.1. Keys

Each curve is made up of a set of key points, or keys for short. Taken all together, the keys define the curve. A key is identified by the combination of the ID of the curve to which it belongs, and an ID number for the key itself. A key is defined at a single position along the curve, and specifies both the value and two helper "wings" that define how the value is changing before and after they key. For a 2D curve, the wings specify the angle at which the curve enters and leaves the key point. To summarize, we have:

(real64 pos,real64[DIM] value)

The key's position on the curve. This is an actual point, that the curve passes through. No two keys can have the same pos value, they must all be unique. Keys can occur in any order, there is no relation between the value of a key's numerical ID and its pos field.

(uint32[DIM] pre_pos,real64[DIM] pre_delta)

Specification for one of the "wings" of the curve point. This wing gives the direction of the curve as it approaches the point from the left. In relative format, see below for how to compute actual control point.

(uint32[DIM] post_pos,real64[DIM] post_delta)

Specification for the other "wing" on the curve point. This wing gives the direction in which the curve exits the point, to the right. In relative format, see below for how to compute actual control point.

In the above, a type name followed by [DIM] indicates that the field being described is a vector, with DIM dimensions. All fields of a key are such, except the position. The absolute position of a key is the same for all of its value's dimensions. The value of DIM is set when the curve is created.

With these basic definitions out of the way, it is time to discuss how a curve is actually evaluated.

2.10.1.2. Evaluating a Curve

To form the Bézier curve, the collection of keys is sorted by the pos field. This should create a strictly increasing sequence of key points, all of which are passed through by the curve. Each segment of the curve (between two key points) needs two additional control points to be able to be evaluated as a Bézier curve.

The following figure illustrates a part of a one-dimensional curve, showing three adjacent keys:

Figure 2-4. Keys in a Curve

The middle key, labeled p1, has its two wings (the short dashed lines ending in smaller filled circles) shown. The first and last keys have only one wing each shown, to make the figure a little bit less complex. This is merely an omission from the figure, there is no way to actually omit wings for a key point in the Verse data model.

The wings are not immediately defined in the same coordinate space as the curve itself. The X component of a wing is given as a uint32 integer, and although the Y component is a real64 just as the curve point, it is not immediately compatible. To go from this specialized representation to coordinates in the key point system, a few simple computations are needed.

The position part of a wing should be interpreted as a fraction between 0 and 1, encoded as a 32-bit unsigned integer where 0 represents 0 and 0xFFFFFFFF represents 1. This fraction is used to scale the distance to the previous or next key, and the resulting value is subtracted (for the prev-wing) or added (for the post-wing) to the key's position to end up with the actual control point coordinate.

Once the control point's X coordinate is known from the above computation, the value-deltas can be used to compute the Y coordinate by multiplying with the same scaled distance as used for the X component, and then adding (or subtracting) it to the key's Y coordinate.

So, to summarize, the prev-control point is computed like this:

  • Let d be the (negative) distance from this key to the previous, in the X dimension.

  • Let p be the fraction from the prev_pos field, i.e. the value of the prev_pos field divided by 0xFFFFFFFF. This is in the range [0,1].

  • The control point is then (pos + d * p, value + d * prev_value).

The post-control point is computed in a similar fashion but with d positive. Once this has been done, we have the four points needed to compute a single Bézier curve segment: the curve from e.g. p1 to p2 uses p1 as its starting position, p2 as its end, and uses p1's post-point and p2's prev-point as its two control points.

The relative way in which the control points are defined, using the wings, guarantees that the curve reacts nicely to having new keys inserted between existing ones, and that the two wings can never be on the wrong side of their parent key point. It is not possible to construct a curve with an "S-shape" using this system, which keeps the curves well-defined for all X-values.

The above computations are simply repeated for all the dimensions of the curve.

2.10.1.3. Edges

The curve needs to extend to infinity in both directions, although there of course cannot be that many keys. It also needs to be well-defined even if there is only a single key, which is not (according to the evaluation rules above) enough to form Bézier curves.

The way this is resolved, is to simply introduce two implicit keys in all curves: one at a X distance of 1 to the left of the first key, and one at a X distance of 1 to the right of the last one. The constant 1 was chosen because in general the X dimension of curves is time, expressed in seconds, and for many movements one second is a reasonable amount of time.

The curve is linear to the left of the first key, using that key's pre-wing to define the angle, and then linear again to the right of the last key, using it's post-wing.

2.10.1.4. Using Curves

How curves are connected to the rest of the data model for actual use is a bit of a research issue at the moment; we have some ideas that will appear in the spec and implementation soon.