This is the second part of the series dedicated to the catenary, the mathematical object used to model hanging wires, cables and chains. This post will show how to implement catenaries in a game engine like Unity.
You can find the Unity package to create catenaries in Unity at the end of the post.
In the previous part of this short online course, we have introduced catenaries. A catenary is a mathematical objects that can be used to model chains anchored between two points.
The simplest equation for a catenary is expressed in terms of , the hyperbolic cosine. Loosely speaking, that is the equivalent of the more well-known cosine function, but on a hyperbola rather than a circle.
The equation of a catenary is:
and has three parameter:
- : the size/scale;
- : the horizontal shift;
- : the vertical shift.
Since many games features hanging wires and chains, getting catenaries right is pretty much critical. A friendly tool should allow to place a chains from three pieces of information:
- Two points, and , which the chain has to pass through;
- The length of the chain between and is .
We can satisfy these constraints by carefully selecting the three parameters of (1):
One major problem is , which unfortunately cannot be expressed in a closed-form. In a nuthsell, it means that there is no easy equation that can be derived to calculate as we did for and .
The rest of this post will explore alternative ways to calculate .
While it is true that there is no closed-form for , it is possible to derive the following equation:
As it turns out, this is a transcendental equation from which cannot be extracted directly. This means that, no matter how hard we try, it is not possible to rewrite (5) in such such a way that the equation looks like , where does not appear on the right-hand side. The best we can do is to bring all of the variables on one side. There might be way to rewrite (5) that allow to do that, but they will all require an infinite number of operations (such as an infinite series or an integral).
For this reason, we need a different way must be taken in order to find the value of . Luckily for us, this particular transcendental equation is easy to solve.
Geometrically speaking, solving (5) is not that complicated: equating two functions means plotting and finding the point in which they touch. This is particularly easy to visualise for (5), since the left-hand side of the equation () is a constant value, which we can imagine as a straight, horizontal line. At some point, it will intersect the curve drawn by the right-hand side of equation (5) (). The x-coordinate of that point is the elusive value of we are looking for.
The chart below plots the two sides of (5) when and , which results in a value of approximately equal to :
In general, finding the intersections of two functions is a rather complex problems, and there are is guarantee that a single point exists. In this specific case, however, we can guarantee that exactly one solution in the range does exist.
We can prove that by studying both functions involved:
- is a constant value which is the result of a square root; it means it is strictly positive;
- is monotonically decreasing in the interval , and tends to when tends to .
This means that, at some point, will decrease enough to reach .
With this knowledge, we can already come up with a simple algorithm to estimate : starting with as a first guess, we can increase its value by a small amount until :
const double IntervalStep = 0.01;
double a = 0;
a += IntervalStep;
while (Math.Sqrt(Math.Pow(l, 2) - Math.Pow(v, 2)) < 2 * a * Math.sinh(h/(2*a)));
The precision of this method can be increased by using a smaller increment for
While this works, it is very slow and it can take several tens of thousands of iterations, even for a relatively small catenary.
A better approach relies on two steps. First, we can find a rough estimate using the method shown above (for instance, using
IntervalStep = 1.0;). This helps us finding an interval an interval in which the right value of will definitely lie. Then, we can run a more sophisticated approach to refine the our guess.
There are many techniques to find the solution of an equation that lies in an interval. In this case, a simple and effective one is the bisection method, which many programmers will recognise as a variant of the binary search algorithm.
We can understand how it work with the help of a simple example. Let’s imagine that you are trying to find a specific word in a dictionary. Your best guess is to open the dictionary on an arbitrary page: let’s say exactly in the middle. Perhaps you have been lucky, and the word you were searching for is just there; more likely, it will not. But since al words are in order, you can now tell in which half of the dictionary you need to keep searching.
Now, you can ignore the other half and repeat the procedure again, opening the dictionary in the middle of the section you know has to contain the word. By repeating this many times, you will eventually reach to the page containing the word you are looking for. With every iteration, the binary search algorithm halves the search space. This is way more efficient than linearly searching for the desires item in a list starting from its first element.
The same method can be used here. We have an interval in which the solution should be (let’s call it
a_next), and we can iteratively split it in two halves, repeating the process until the interval size is arbitrarily small. When this is running on an actual machine, it is very unlikely we will find the exact, theoretical value of . This is because of rounding errors resulted from the way modern computer are storing numbers. You can read more about this on a series of articles dedicated to Floating-Point Arithmetic.
In our case, we know which half of the interval we should keep searching, because on one side is greater than , while on the other the opposite is true.
const float Precision = 0.0001;
double a_prev = a - IntervalStep;
double a_next = a;
a = (a_prev + a_next) / 2f;
if (Math.Sqrt(Math.Pow(l, 2) - Math.Pow(v, 2)) < 2 * a * Math.sinh(h/(2*a)))
a_prev = a;
a_next = a;
} while (a_next - a_prev > Precision);
If we want to be extra safe, we could also add another condition to make sure that we exit after a maximum numbers of iterations.
A Practical Example
The interactive charts below allow to play with the parameters of a catenary: one of its anchor points, and the chain length . In the second chart you can see both and plotted. The x-coordinate of point in which they touch is .
This is facilitated by the fact that is a rather well-behaved function. Since it is monotonically decreasing, and the part we are interested on is in the first quadrant. Starting with a low estimate of , we can simply increase its value little by little, until the value of the function becomes smaller than . A more sophisticated approach could use binary search to speed up the search.
From 2D to 3D…
This post concludes our journey to explore the mathematics and implementation of catenaries in videogame.
Download Unity PackageBecome a Patron!
There are two different Unity packages available for this tutorial. They contain a simple library to draw efficiently catenaries, which you can use in your games. Both packages are available through Patreon.
The Standard package contains the scripts to draw catenaries in 3D and 3D, along with a test scene. The Advanced package contains support for rigged models (such as corded cables or chains), along with some advanced code to sample catenaries uniformly.