Previous Bezier Spline examples have illustrated how to draw a spline directly with pre-generated data in MXML. In some applications, it is desirable to define the Bezier Spline in MXML, but generate knots (control points) dynamically.

The following diagram shows our infamous arrow generated one segment at a time.

The demo allows one knot to be added or deleted (starting with the minimum of three) until the full set is defined. You may optionally close the spline to complete the drawing. Once the spline is closed, however, it is not recommended to remove and add knots because of the internal bookkeeping inside the Bezier Spline.

The control points are dynamically redrawn based on the internally generated propertyChange event, which also controls the spline redraw. This should illustrate the basics of defining a spline in MXML and dynamically generating knots via script. Download the MXML file here.

One of the many features I like about Degrafa is the ability to draw the same object in multiple targets. Let’s take our friendly arrow spline from the introduction to spline series. If an arrow is worth drawing once, we know the design team will eventually want to draw multiple arrows 🙂

This is a beginner tutorial on how to draw into multiple targets and add a simple rotation transform. You should understand how to setup a Flex project and work with Degrafa. Since this example works with splines, you will also need at least Degrafa Beta 3.

If you followed the introudction to splines series, you noticed that the ‘target’ that Degrafa draws objects into was actually an array. This allows multiple targets to be specified for the same drawing. Give the following code a try,

Since the transform is applied inside the BezierSpline tag, it is applied to the spline drawn into each individual target. As the transform is modified, the modification is propagated to all targets without any direct action on your part.

Notice that the original arrow is drawn pointing downward. The following screen shot shows how all four arrows are modified by the slider (some of the screen space was cropped to save space here).

That’s all you need to do to draw multiple copies of the same item and manipulate all of the targets with a single transform. Experiment and have fun!

Continuing from Part IX, one of the features of building the Bezier spline with cubic segments (individual cubic Bezier curves) is some flexibility in how the control cages are constructed. The mathematical details are discussed in this TechNote. At a high level, consider the spline as a rope that is passed through rings at each knot. Suppose you could ‘pull’ on the rope at any ring. What would happen to the rope?

We would expect the rope to get tighter and tighter until it reached some point that it could not be pulled any further (without breaking). Intuitively, we would call this tension. Bezier curves (individual or composite) do not naturally have tension parameters (as say a cardinal spline). We can fake a tension setting as a natural consequence of assigning control points.

In the Singularity Bezier spline, the tension setting is a map of a tension scale to parameter values. The tension scale varies from 1 (loose) to 5 (tight). The internal parameter map is open for experimentation and some limitations are discussed in the above TechNote.

From a usability standpoint, all the average user need be concerned with is the tension setting from 1-5. The default value is 1, which can be thought of as having the rope pass ‘loosely’ through the rings. On average, the spline appears to have more curvature when passing through the knots.

The tension parameter is exposed to MXML, and the following illustrates setting to it maximum value of 5,

As you work through the code, you may notice a parameter or two such as ‘quality’ that appear to have no effect on the output. This parameter in particular was an artifact of the Singularity cubic Bezier spline that was built on top of the FastBezier class. The quality parameter controlled the number of subdivision steps (used to approximate the cubic Bezier with a small number of quadratic Bezier). This parameter allowed render quality to be traded for performance. It will be removed from future versions of the Degrafa Bezier Spline.

This completes the basic introduction to splines in Degrafa. Future posts will provide demos of more advanced usage of the Degrafa splines and illustration of new features as they are added.

Continuing from Part VIII, what happens if we want to close the arrow outline? We have discussed auto-closure for the Bezier spline in a previous post and this feature is available in the Degrafa spline. Not only is it available, it is exposed to direct manipulation in MXML. Simply modify the <BezierSpline> tag as follows,

There was a small bug in the initial port, so make sure you first update the code from SVN. This produces the following drawing,

Degrafa splines are ‘open’ by default. You must specify closure (if it is supported by a particular spline) by setting the autoClose parameter to true. The closure is automatic; no additional knots or programming action are required. The diagram below illustrates construction of the individual cubic Bezier control points.

An additional knot is automatically added to the data set to be interpolated. The added knot is a duplicate of the first interpolation point. An additional segment (cubic Bezier curve) is added to the spline. Recall that the spline is sometimes called a composite Bezier curve as it is composed of independent cubic Bezier curves with continuity conditions imposed at the joins.

The control points for the final cubic Bezier curve in the composition are computed so that the slope of the in-tangent to the final knot matches that of the out-tangent of the first knot. The same condition is applied to the in- and out-tangents of the final knot. This preserves continuity at all joins.

Another parameter is available to adjust the visual characteristics of the interpolated spline. In the spirit of a weekend cliffhanger, think about a rope that is threaded through a ring at each knot location. What would happen if you pulled that rope tighter?

Continuing from Part VII, the BezierSpline tag provides a means to set inputs to the Degrafa BezierSpline class. All splines in Degrafa that are explicitly called splines are purely interpolative; that is they interpolate an arbitrary set of points. It is okay to call a quadratic or cubic Bezier curve a spline. These parametric curves interpolate the first and last points in a set. Interior points influence the shape of the curve, but they are not interpolated. Even so, quad and cubic Beziers are still essentially splines.

So, when we specifically call a Degrafa curve a ‘spline’, the reference will always be to a parametric curve that completely interpolates an arbitrary point set. That point set is described in the ‘data’ attribute of the tag and drawn into a target that is bound to the Flex Canvas with the id of ‘theTarget.’

The <stroke> tag describes how to draw the Bezier spline as it is contained inside the BezierSpline tag. In typical Degrafa fashion, we could add a <fill> tag after the <stroke> to specify a fill for the drawing. Alternatively, we can create arbitrary sets of fills and bind them to a ‘fill’ attribute to create reusable fills. For example,

Notice that mathematically, the spline does what it is told; it interpolates the input points and Degrafa draws the spline with the specified stroke. Degrafa also dutifully draws the fill based on the stroke outline and specified point set, but there is an implied closure to the fill that is not present in the spline outline.

The next post will discuss the topics of closure and spline tension.

Continuing from Part VI, it’s time to have some fun. Let’s draw a cubic Bezier spline using nothing but MXML. As an old-school assembly language programmer, I find this a bit exciting. All the complexity and detail from both a mathematical and programming standpoint are completly hidden. What might traditionally be exposed to programmers through a documented API is now exposed to people who may have little traditional programming experience entirely through an XML-style syntax.

First of all, you need Degrafa Beta 3. You also need a bit of experience with how to setup a Degrafa project and a minimal amount of Flex experience. Fortunately, there are a great number of samples at multiple levels of complexity at the Degrafa learning page.

Let’s start with a simple outline of a curvy arrow, created by Jason Hawryluk. Jason did the initial cubic Bezier spline port, starting from a standalone code base I created that was divorced from any dependency on non-essential classes in the Singularity package. For not having a computational geometry background, he did an incredible job and deserves some serious props for getting this effort off the ground.

Create a new Flex project and try the following code,

If you run the application, your output should appear similar to the following.

I ran the same data against the Singularity cubic Bezier spline which has additional capability to display the knots and control cages for the individual cubic Bezier curves comprising the spline. These are illustrated below.

Notice how we were able to draw a relatively complex shape by specifying only a few knots through which the spline must pass. The spline computations do all the work and the Degrafa framework exposes relevant properties to control the spline through MXML.

Well, that’s enough to get started. In subsequent posts, we will deconstruct the basic code and make some modifications.

I’ll be getting into the Degrafa demos business pretty soon, and will be adding a Degrafa page to the blog just for tutorial and related links. If you haven’t looked into Degrafa or are curious about how to get started, then check out the excellent introduction at InsideRIA. There is a great samples section at the Degraf site as well as a complete learning page.

Some misc. tutorials you might find of interest (a few samples from my Degrafa bookmarks – yes, they need to be posted to Delicious),

Continuing from this post, this is the last in the deconstruction of the basic algorithm for distributing text along an arc-length parameterized spline. The final case to consider is when the amount of text to distribute excedes the physical boundary of the spline curve. A simple way to handle this caase is to estimate the slope towards the end of the curve and continue the text in a straight line in the direction of that estimate. This is the idea behind the else block,

else
{
// text extends beyond end of spline - slope estimate towards
// end of spline to continue along in a straight line
if( cacheRotation )
{
dX = endX - __spline.getX(0.92);
dY = endY - __spline.getY(0.92);
d = Math.sqrt(dX*dX + dY*dY);
uX = dX/d;
uY = dY/d;
rotation = Math.atan2(dY, dX)*Consts.RAD_TO_DEG;
cacheRotation = false;
}
tf.rotation = rotation;
myWidth = tf.textWidth;
myHeight = tf.textHeight;
// distance along line from end of of spline to current
// text starting position
d = (s-1)*arcLength;
myX = endX + d*uX;
myY = endY + d*uY;
tf.x = myX + uY*myHeight;
tf.y = myY - uX*myHeight;
}

which handles the case when the current s-parameter is greater than 1 (meaning the next letter is placed beyond the physical boundary of the spline). The advantage of extrapolating along a straight line is that the rotation value can be computed once and re-used for each letter. There is also a constant normal vector. All these values can be computed once (the first time through the else block) and continually re-used. This is the idea behind the cacheRotation value that is initialized to true.

The Δx and Δy values are estimated by,
dX = endX – __spline.getX(0.92);
dY = endY – __spline.getY(0.92);

The 0.92 value is arbitrary. A more sophisticated algorithm might use the mean of two or more estimates. The exact slope at s=1 sounds reasonable, but visually the text often flows better when using some sort of average estimate.

Notice that every time through the else block, the current TextField’s rotation property is set to the same value,

The amount by which the current s-parameter exceeds 1.0 provides an indication of how far along the line to place the text,

d = (s-1)*arcLength;
myX = endX + d*uX;
myY = endY + d*uY;
tf.x = myX + uY*myHeight;
tf.y = myY - uX*myHeight;

The computations are the same as before, except that the unit and normal vectors have constant orientation. They are ‘shifted’ along the straight line using the spline endpoint plus the distance along the straight line. After that, TextField positioning is the same as before.

I hope this series of posts provides a good high-level deconstruction of the simple algorithm. Understanding the algorithm at this level is necessary before adding numerous adjustments and refinements to take font metrics, punctuation, and other issues into account.

Continuing the code deconstruction from this post, the second pass of the algorithm is split into two parts. The first distributes text up to the final knot in the spline. The second part handles the case where the text extends beyond the natural spline boundaries. This is done by using the curve’s orientation toward the end of the spline to extend the text in a straight line in that direction, as shown in this post.

The code segment for the first part of phase 2 is,

// second pass - place text along spline
var s:Number = 0;
var endX:Number = __spline.getX(1);
var endY:Number = __spline.getY(1);
var cacheRotation:Boolean = true;
var rotation:Number = 0;
for( i=0; i<__myText.length; ++i )
{
tf = __letters[i];
if( s <= 1 )
{
var myX:Number = __spline.getX(s);
var myY:Number = __spline.getY(s);
if( s < 0.95 )
{
var dX:Number = __spline.getX(s+0.05) - myX;
var dY:Number = __spline.getY(s+0.05) - myY;
}
else
{
dX = endX - __spline.getX(0.94);
dY = endY - __spline.getY(0.94);
}
// normalize
var d:Number = Math.sqrt(dX*dX + dY*dY);
var uX:Number = dX/d;
var uY:Number = dY/d;
tf.rotation = Math.atan2(dY, dX)*Consts.RAD_TO_DEG;
var myWidth:Number = tf.textWidth;
var myHeight:Number = tf.textHeight;
tf.x = myX + myHeight*uY;
tf.y = myY - myHeight*uX;
}

The variable, s, denotes arc length along the spline. The variables, endX and endY record the coordinates of the spline at s=1 (or the endpoints). The Boolean variable, cacheRotation istrue if we are using a constant (precomputed) rotation value, which is the case when the text extends beyond the end of the spline. The rotation variable denotes the rotation of the TextField for each letter distributed along the spline.

This block of code,

if( s <= 1 )
{
var myX:Number = __spline.getX(s);
var myY:Number = __spline.getY(s);
if( s < 0.95 )
{
var dX:Number = __spline.getX(s+0.05) - myX;
var dY:Number = __spline.getY(s+0.05) - myY;
}

estimates the tangent to the curve at the current s parameter. The cumulative text width is multiplied by the inverse of arc-length to estimate the s-parameter corresponding to the beginning of each letter, as previously described. We could use the Singularity method that returns the derivative of the spline at the parameter value (which is the slope of the tangent), however, I wanted this code block to be usable with other packages that draw Bezier curves. Most of those packages do not return derivative information, so it is estimated by dy/dx ~ Δy/Δx for small Δx.

The tangent of the spline at s is used to orient the TextField to align with the tangent at that point. This is just a starting-point for aligning the text and works reasonably well except in places where the spline has extremely high curvature. Understanding this first approximation is helpful before delving into more complex algorithms.

A unit vector in the direction of the tangent approximation is computed next and the TextField’s rotation property is set,

var d:Number = Math.sqrt(dX*dX + dY*dY);
var uX:Number = dX/d;
var uY:Number = dY/d;
tf.rotation = Math.atan2(dY, dX)*Consts.RAD_TO_DEG;

Note the use of the atan2() function. The x- and y-components of the unit vector are used to compute the normal vector, which is needed to position the TextField. This will be discussed in the next part of the deconstruction.

Regarding the code listed in my previous post, let’s take one step back and look at the problem from a very high level. Often, it’s easier to understand an approach to solving a problem if you can relate it to something similar, yet simpler. So, suppose your task were to render text one letter at a time with constant spacing (tracking) along a horizontal line. How would you write the program?

Starting at the beginning of the line (minimum x-coordinate if rendering LTR), you would probably place a letter then use its width (and the number of px spacing) to compute the x-coordinate of the next letter. The letter’s height would be used to compute the y-coordinate of the TextField relative to the y-coordinate of the horizontal line. So, if the horiztontal line starts at coordinates (100,100) and the first letter happens to be 22px tall and 20px wide and there is a constant 15px spacing between letters, a simple algorithm for placing that letter would be to position the TextField at (100, 100-22). The second letter would be positioned at (100+20+15, 100-22). In this simple approach, puncutation and text metrics are not taken into account. No consideration of lowercase descending characters, etc.

Next, suppose that the line is not horizontal. How does the algorithm change? Instead of positioning letters at strictly horizontal increments, we now have to move in increments along the line. So, if a letter is 20px wide with a 15px spacing, we need to increment a distance of 35px along the line in order to place the next letter. The easiest way to accomplish this task is to use a unit vector in the direction of the line.

We also have to orient (rotate) the TextField to match the slope of the line. The angle the unit vector makes with the horizontal provides this information. The TextField is offset from the line along the normal (perpendicular to the line) the same distance as the text height. Since we know the unit vector, we also know the normal vector as the rotation matrix for a CCW rotation of PI/2 is trivial.

In a simplistic sense, we are using Frenet-Serret coordinates (or frame – without the binormal) to position the text and the unit vector information to orient the text.

The final step in the overview is to translate the concept of a line with general orientation to a spline curve. The concept of distance along the line is now translated to distance along the spline. This is why it is important that the spline be parameterized on arc length. Arc-length parameterization allows us to request specific distances along the curve to compute the tangent and normal direction vectors. Once we have these vectors, the approach is exactly the same as the straight line.

In subsequent posts, I will deconstruct how the posted code accomplishes the simple algorithm for text distribution along a cubic Bezier spline.