If you are using mathematical curves as input to skinning for procedural geometry, or as paths followed by an agent or a camera in game development, you might have suffered the effects of nonuniform sampling along the curve. The input parameter of the function describing your curve is not, in general, a fraction of the arc length along the curve. This translates to weirdly stretched skins on your procedural model, or non-uniform movement along your paths. Today, we are solving this problem for a specific type of curves for which a closed-form expression exists. In the next part we will address the same problem for a more uncooperative class of curves.
General case
Arbitrary curve in
Let a parametric curve be described by a continuously differentiable and injective function for . Let be the curve parameter. Then, we can define the arc length of the curve up to the value of the parameter as:
The full length of the curve is obtained by integrating over the whole interval :
If we want to find the value of such that the arc length has a given value, then all we need to do in theory, is to compute the inverse function . Then the function is parametrized in terms of the arc length. If we need the value that takes exactly at the midpoint of the curve , for example, then we calculate . We can additionaly compose this function with , so that we just need to feed it the length fraction as a number in :
And the value of at the midpoint of is simply .
Planar curves defined by function graphs
To set these ideas on a special case, let’s consider the curve obtained by graphing some arbitrary -function. Let a curve be specified as the graph of a function . Then all the points belonging to this graph are of the form:
Then takes the form:
If we’re looking for a closed-form expression, then must be expressible in terms of elementary functions, and the expression must be invertible. This is not possible in general as is often an elliptic integral, as you might suspect from seing that bad boy radical in the integral . And if it is not the case, then there is always the possibility that no closed-form expression exists for the inverse of . In fact, there are only a handful of curves for which a closed-form arc length expression exists, and we’ll have a look at one of them in the following section.
On a side note, we have some adjustments to make to if we intend to plot using the normalized arc length :
Arc length parametrization of the catenary
A catenary is the shape assumed by a hanging chain or rope, supported only at its both ends, under the effect of its own weight. If you ever saw an electric cable hanging between two posts, or the Gateway Arch in St. Louis, you know what a catenary looks like. It resembles a parabola, but a more rounded version of it. I won’t prove to you why hanging cables assume this shape, but I encourage you to have a look at a proof involving variational calculus if you’re interested in physics, it’s worth it. We will assume that the catenary is given by the following general expression:
That’s right, a good ol’ hyperbolic cosine! Here, , and are curve parameters. It is more practical, in terms of game development, to think of the anchor points and and the total length as the input parameters to this problem, as it makes more sense to control them in an editor, rather than the curve parameters themselves. So , and will be expressed later on as functions of , and .
Solving for the arc length
Let and be the position of the anchor points in the plane. We suppose that and . If it’s not the case, you can always swap and and reflect the curve about the midline. We also note and . We suppose that the total length of the catenary verifies the inequality as the curve cannot be shorter than the straight line connecting the anchor points.
By the chain rule, we have that:
Thus, according to , the arc length of the catenary is given by:
And because we have a value for the integration constant:
So the final expression for is:
And substituting gives an expression for the total length :
Because we have the insolent good fortune to work with such a nicely behaved function, we can invert to express as a function of :
with . And with this we can easily obtain an arc length parametrized using :
Determining parameters
If we want to find the value of such that the catenary between and is of length , then we need to solve the following equation:
Proof [click to expand]
By and the definition of we have:
So using , the definition of , and some hyperbolic trigonometric identities (difference of squares, of the difference and double argument for ) we can expand like so:
And by taking the square root on both sides we get .
Now, the issue with this one, is that it’s a transcendental equation in , so it can only be solved numerically. A few iterations of Newton-Raphson will do the trick:
Our update scheme requires us to input an initial guess for the value of , and the convergence is quite sensitive so we would better not be too far off. In practice, I found that can be selected by iteratively incrementing by a geometrically growing step size until the sign of changes. This works because only has a single zero, as it’s monotonically decreasing. More on that later. Then, and are given by:
Proof [click to expand]
First, the total length of the curve can be rearranged a bit, using some hyperbolic trigonometry and the definition of :
Plugging and in we get the following system:
Subtracting on both sides and by definition of , we get:
Multiplying this by written in a fancy way, we recognize and a hyperbolic tangent :
And we can express after some massaging:
And from there, recovering is a piece of cake.
Proof [click to expand]
Summing on both sides of (expand the proof for ) we get:
And multiplying the second term by a smart, we recognize and a hyperbolic cotangent:
which completes our proof.
Implementation
My original implementation is scattered across multiple C++ files in a project called Kibble, located on my personal GitHub account. You can have a look at this folder, more specifically the files named catenary.h/cpp and numeric.h/cpp, if you’re looking for a C++ implementation. There is also an example here on how to use the implementation. Kibble is a utility library I’ve been working on for some time, that features many software components I end up always reusing in my game development projects.
But I find it more practical to plot stuff in Python, so let’s have some good fun with numpy, scipy and matplotlib:
We move on to determining the values of , , and from the input parameters. We will use scipy.optimize.newton() to find by solving , so we must implement functions for and :
We also need an initial guess for the Newton-Raphson algorithm to work:
defnr_first_guess(ff,start_x,start_step,alpha):xx=start_xstep=start_stepyy=ff(xx)yy_prev=yywhileyy*yy_prev>0:yy_prev=yyxx+=stepstep*=alphayy=ff(xx)# Backtrack a bit
returnxx-0.5*step/alpha
This function iteratively increments the value of the argument xx fed to an input function ff. The steps themselves grow geometrically with a common factor alpha which is bigger than . We break from the loop when the sign of the function has changed. At this point, we know we’re past the zero of the input function, so we interpolate between the current and previous value of xx to output a more accurate value for the zero. This is a quick and dirty solution, you can get fancier with a binary search if you need to.
Then, using the previous functions, as well as formulas , and , we are able to compute the values of all the parameters that the catenary() function expects as inputs:
The values fed to nr_first_guess() are somewhat arbitrary, but we need to keep in mind that we should not start too close to as diverges there. And basically, we’re done! We can compute catenary parameters like so:
Note that we calculate the minimal length as the Euclidean distance between and , so we can make sure the input distance L will be greater than that. To get points on the catenary using the initial parametrization we would do:
So let’s plot a few catenary curves with varying lengths, and compare the two parametrizations:
You can see how the “default” parametrization leads to uneven segment lengths, the effect is more pronounced for longer curves. The segments obtained using the arc length parametrization on the other hand, are of equal lengths. If you were to skin a curve like this in a game, the arc length parametrization would completly get rid of texture stretching artifacts. Do note however that the segments tend to deviate more from the curve, so a higher number of sampling points must be chosen in order to retain a rounded shape. Have a look at the full source code (link below) if you want more detail on how to plot this.
In the next article, we are going to implement an arc length parametrization for cubic Hermite splines. The problem will be solved entirely numerically as no closed-form expression exists for these curves. Hope you enjoyed, see you on the next one!
The comment section requires the Utterances cookie in order to work properly.
If you want to see people's comments or post a comment yourself,
please enable the Utterances cookie here.