7.2 Surfaces
Until now, we have used Khepri to create curves, which we normally visualize in two dimensions, for example in the \(XY\) plane, or to create solids, which we see in three dimensions. Now, we will address the creation of surfaces.
There are many ways for Khepri to create a surface. Many of the functions that produce closed curves have a version that produces a surface delimited by those closed curves, e.g., surface_circle, surface_rectangle, surface_polygon, and surface_regular_polygon. These functions receive exactly the same arguments as, respectively, the functions circle, rectangle, polygon, and regular_polygon, but produce surfaces instead of curves. Besides these ones, there is also the surface_arc function that produces a surface delimited by an arc and by the radii between the extremities and the center of the arc. Finally, we have the surface function, which receives a closed curve or an array of curves and produces a surface delimited by them. In fact, we have:
surface_circle(ldots)\(\equiv\)surface(circle(ldots))
surface_rectangle(ldots)\(\equiv\)surface(rectangle(ldots))
surface_polygon(ldots)\(\equiv\)surface(polygon(ldots))
The equivalences for the remaining functions are established in the same way.
As with solids, surfaces can also be combined with the union, intersection, and subtraction operations to create more complex surfaces. For example, let us consider the following union between a triangular surface and a circular surface:
union(surface_polygon(xy(0, 0), xy(2, 0), xy(1, 1)),
surface_circle(xy(1, 1), 0.5))
with the result displayed on the upper left corner of this figure.
Combinations between a triangular surface and a circular surface. On the upper left corner, we have their union and on the right their intersection. On the lower left corner, we have the subtraction between the first and the second surfaces and, on the right, between the second and the first.
If we had chosen the intersection instead of the union operation, the result would be the surface represented in the upper right corner of this figure. The subtraction of the circle from the triangle is illustrated in the lower left corner of this figure and, since subtraction is not a commutative operation, the lower right corner shows the subtraction of the triangle from the circle.
7.2.1 Trefoils, Quatrefoils and Other Foils
The trefoil is an architectural element that had a widespread use during the Gothic period. It is an ornament made up of three tangent circles arranged around a center, usually placed on the top of Gothic windows, but it can be found in several other places. Besides the trefoil, Gothic architecture also explores the quatrefoil, the cinquefoil and other "foils". In this figure we present several examples of these elements.
Trefoils, quatrefoils, cinquefoils and other "foils" in a window of the Cathedral of Saint Peter in Exeter, England. Photograph by Chris Last.
In this section, we will use the operations that allow the creation and combination of surfaces to build trefoils, quatrefoils and, more generically, \(n\)-foils. For now, we start by considering the trefoil.
The parameters of a trefoil are illustrated in a schema presented in this figure. In that figure, we can identify \(r_e\) as the external radius of the trefoil, \(r_f\) as the radius of each leaf of the trefoil, \(\rho\) as the distance from the center of each leaf to the center of the trefoil and \(r_i\) as the radius of the inner circle of the trefoil. The position \(P\) will be the center of the trefoil. Since the trefoil divides a circumference into three equal parts, the angle \(\alpha\) will have to be half of a third of the circumference, in other words, \(\alpha=\frac{\pi}{3}\). In the case of a quatrefoil, we will have \(\alpha=\frac{\pi}{4}\) and, in the general case of an \(n\)-foil, we will have \(\alpha=\frac{\pi}{n}\).
Parameters of a trefoil.
Applying the trigonometric relations, we can relate the trefoil parameters with each other:
\[r_f=\rho\sin\alpha\] \[r_i=\rho\cos\alpha\] \[\rho+r_f=r_e\]
If we assume that the fundamental parameter is the trefoil’s exterior radius, \(r_e\), we can deduce that:
\[\rho=\frac{r_e}{1+\sin\frac{\pi}{n}}\] \[r_f=\frac{r_e}{1+\frac{1}{\sin\frac{\pi}{n}}}\] \[r_i=r_e\frac{\cos\frac{\pi}{n}}{1+\sin\frac{\pi}{n}}\]
From these equations, we can decompose the creation process of an \(n\)-foil as the union of a sequence of circular surfaces of radius \(r_f\) arranged circularly around an inner central circle of radius \(r_i\). Transcribing to Julia, we have:
nfoil(p, re, n) =
union(outer_circles(p, re, n),
inner_circle(p, re, n))
The function inner_circle is defined as:
inner_circle(p, re, n) =
surface_circle(
p,
re*cos(pi/n)/(1 + sin(pi/n)))
For the function outer_circles, which is responsible for creating the circularly arranged leaves, we will consider the use of polar coordinates. Each leaf (of radius \(r_i\)) will be placed in a polar coordinate determined by the radius \(\rho\) and by an angle \(\phi\) that we will recursively increment with \(\Delta_\phi=\frac{2\pi}{n}\). For that, we will define a new function to implement this process:
radial_circles(p, rho, phi, dphi, rf, n) =
if n == 0
???
else
union(
surface_circle(p + vpol(rho, phi), rf),
radial_circles(p, rho, phi + dphi, dphi, rf, n - 1))
end
The question now is what the function should return when n is zero. To better understand the problem, imagine we name the n circles as c1, c2, ..., cn. Given that, during the recursion, the function will leave the unions pending, when we reach the stopping condition we have:
union(
c1
union(
c2
...
union(
cn
???)))
It is now clear that ??? has to be something that can be used as an argument of a union and that does not affect the pending unions. This implies that ??? must be the neutral element of the union, i.e., the empty set \(\varnothing\). It is precisely for that purpose that Khepri provides the empty_shape function. When invoked, the empty_shape function produces an empty region, i.e., an empty set of points in space, literally representing an empty shape, which, therefore, is a neutral element in the union of shapes.
Using this function, we can already write the complete algorithm:
radial_circles(p, rho, phi, dphi, rf, n) =
if n == 0
empty_shape()
else
union(
surface_circle(p + vpol(rho, phi), rf),
radial_circles(p, rho, phi + dphi, dphi, rf, n - 1))
end
We can now define the outer_circles function that invokes the previous one with the precalculated values of \(\rho\), \(\Delta_\phi\) and \(r_f\). To simplify, we will consider an initial angle \(\phi\) of zero. Hence, we have:
outer_circles(p, re, n) =
radial_circles(
p,
re/(1 + sin(pi/n)),
0,
2*pi/n,
re/(1 + 1/sin(pi/n)),
n)
With these functions, we can create the trefoil and the quatrefoil, presented in this figure, which result from the following expressions:
nfoil(xy(0, 0), 1, 3)
nfoil(xy(2.5, 0), 1, 4)
A trefoil and a quatrefoil.
Naturally, we can use the same function to generate other variants. This figure shows a cinquefoil, a sexfoil, a septfoil, and so on.
Foils with increasing number of leaves. From top to bottom and from left to right we have a cinquefoil, a sexfoil, a septfoil, an octofoil, and so on.
Having a generic mechanism for constructing \(n\)-foils, it is tempting to explore its limits, particularly when we reduce the number of leaves to two. Unfortunately, when we attempt to create a bifoil, we trigger an error in the function inner_circle. The error occurs because, as the number of foils decreases, the radius of the inner circle diminishes until it becomes zero. In fact, for \(n=2\), the radius of the inner circle is \[r_i=r_e\frac{\cos\frac{\pi}{2}}{1+\sin\frac{\pi}{2}} = r_e\frac{0}{2}=0\]
We can easily fix the problem by inserting a test in the function n_foil that prevents the union between the leaves and the inner circle when the latter has a zero radius. However, that is not the best option because, from a mathematical point of view, the algorithm for constructing foils is still perfectly correct: it unites an inner circle to a set of leaves. It so happens that, when the inner circle has a zero radius it represents an empty set of points, i.e., an empty shape, and the union of an empty shape with the leaves does not affect the result.
Based on the possibility of creating empty shapes, we can now redefine the function inner_circle in a way that, when the number of leaves is two, the function returns an empty shape:
inner_circle(p, re, n) =
if n == 2
empty_shape()
else
surface_circle(
p,
re*cos(pi/n)/(1 + sin(pi/n)))
end
Using this redefinition of the function, it is now possible to evaluate the expression nfoil(xy(0, 0), 1, 2) without generating any error and producing the correct bifoil, visible in this figure.
A bifoil.
Given that we can create bifoils, trefoils, quatrefoils, and \(n\)-foils, we can also legitimately think about unifoils and zerofoils. Unfortunately, here we bump into mathematical limitations that are more difficult to overcome: when the number of foils is one, the radius \(r_i\) of the inner circle becomes negative, losing all geometric sense; when the number of foils is zero, the situation is equally absurd, as it becomes impossible to compute the angle \(\alpha\) due to a division by zero. For these reasons, the bifoil is the lower limit of the \(n\)-foils.