nextuppreviouscontentsindex
Next:Sets, models, and DIY Up:SvLis Introduction Previous:Fundamental geometric structures

Subsections


 
 

Copyright © 1994, 1996, 1999 Information Geometers Ltd and The University of Bath



Curved solids, surfaces and lines

Solids with simple curved surfaces

Already, in Chapter 1, we have seen that svLis can use cylinders as well as flat surfaces as primitives. In fact, svLis is able to handle a very wide range of primitives with curved surfaces, but let's start with cylinders.

Having looked at the implicit planar half-space inequality at the end of the last chapter, you might imagine that svLis uses the same trick to define a solid cylinder. For example a cylinder of radius 2 parallel to the z-axis and with its axis through the point x = 3, y = 1 would, conventionally, be represented by the inequality:

\begin{displaymath}(x - 3)^2 + (y - 1)^2 - 2^2 \leq 0.\end{displaymath}


 


It is more difficult if the cylinder is not aligned with one of the axes, of course. However, any cylinder could be represented by the general quadric inequality: 

\begin{displaymath}\begin{array}{ccccccccc}a_0 & + & a_1 x & + & a_2 y & + & ... ...\ & + & a_7 xy & + & a_8 xz & + & a_9 yz & \leq & 0.\end{array}\end{displaymath}

If we could be bothered to figure out the right numbers for all the ai values, this equation would also do for ellipsoids, spheres, hyperbolas of revolution, and so on.

However, svLis doesn't use this representation. It uses the planar basis  instead. Consider the disc shown in Figure 10. This is a cross-section through the z-axis-aligned cylinder I just mentioned, and it can be represented by exactly the same inequality:
 


\begin{displaymath}(x - 3)^2 + (y - 1)^2 - 2^2 \leq 0.\end{displaymath}
 
 
 

\begin{figure}% latex2html id marker 1196\epsfysize=80mm\centerline{\epsffile... ...is doesn't affect their shape, ofcourse. \end{minipage}\end{center}\end{figure}


 



Suppose we have two normalized perpendicular implicit lines, L1 and L2, that intersect at the disc's centre. Just as with a normalized (half-)plane, the distance of a point from a line is given simply by substituting the point into the expression for the line. Consider point ${\bf p}$ on the disc's circumference. It is d1 away from L1 and d2 away from L2. Pythagoras' theorem says that d12 + d22 - r2 = 0. As (in this case) r = 2, this means that

L12 + L22 - 22 = 0

must be the equation of the disc's circumference, and that
 


\begin{displaymath}{L_1}^2 + {L_2}^2 - 2^2 \leq 0\end{displaymath}

must be the inequality that defines the whole disc, interior and all. In Figure 10, L1 and L2 are the following functions:

\begin{displaymath}\begin{array}{ccccccc}L_1 & = & -x/\sqrt{2} & + & y/\sqrt{... ...2 & = & x/\sqrt{2} & + & y/\sqrt{2} & - & 2\sqrt{2}.\end{array}\end{displaymath}

However, any two normalized and mutually perpendicular lines that crossed at the disc's centre would do.

This all works just as well in three (or any number of) dimensions as it does in two. To make any cylinder we take two perpendicular planes that intersect at right angles along the cylinder's axis, add their squares, and subtract the square of the radius. If the resulting expression is made less than or equal to zero, this means that:

Why go to all this bother to make polynomial primitive inequalities by doing arithmetic with planes? There are several reasons. The first is that it is often (when you're used to it) simpler to think in these terms than it is to try to work out coefficients in expressions like that for the general quadric on Page [*]. Cooking up two planes that cut along the axis of a cylinder isn't very hard, whereas working with the coefficients usually is. The second reason is that it makes the primitives easy to move around in space--you just translate or rotate the planes, and the shape you've made moves too. The third is that it is more numerically stable  than the ordinary form for polynomials. The fourth and final reason is that it makes the testing of boxes against primitives (see Page [*]) both more efficient and more precise. The piece of svLis that follows constructs a sphere. This operation needs three mutually perpendicular planes intersecting at its centre, but otherwise uses the ideas that we have just seen. Those three planes (xhs, yhs, and zhs) might as well be perpendicular to the coordinate directions as anything else. Next is the function for a cylinder. Most of this is concerned with the vector algebra (using sv_points) needed to construct the two perpendicular planes that intersect at the axis.  Note the brackets needed round $ \wedge $, as explained on Page [*].

The only differences between those two functions and the actual ones in the svLis library are that the library functions also set internal flags  to record the fact that the primitives s and c are special shapes, not just any old quadric, and that the arguments are transferred by reference. Finally in this section, here is a complete list of the simple solid primitives built into svLis:

sv_primitive p_cylinder(sv_line l, sv_real r)
The line is the cylinder's axis, and the sv_real is its radius, as we have just seen.
sv_primitive p_cone(sv_line l, sv_real a)
Once again the line is the axis. The sv_real is the cone's included angle in radians. The apex of the cone is at the line's origin. Note that this constructs the equation of a double cone meeting at the apex. If you only want half of it (and you probably do) then you have to turn it into a set (see Page [*]) and intersect it with a plane through the apex. There's a function to do this.
sv_primitive p_sphere(sv_point p, sv_real r)
The arguments are the centre and radius, again as we have just seen.
sv_primitive p_torus(sv_line l, sv_real big_r, sv_real little_r)
The major circle of the torus (radius big_r) is perpendicular to the line and is centred at its origin. little_r is the minor radius of the torus.
sv_primitive p_cyclide(sv_line l, sv_point sym, sv_real big_r, sv_real little_r, sv_real delta_r
The major circle of the cyclide (radius big_r) is perpendicular to the line and is centred at its origin. little_r - delta_r is the smallest radius of the cyclide; little_r + delta_r is the largest. The point sym defines the direction of symmetry of the cyclide, and thus must not be parallel to the direction of the line.
In addition, as we saw on Page [*], if f is a half-plane  you can turn it into a primitive:

Blends

If   f1, f2, f3, and f4 are planes (there could be more than four, or fewer; this is just an example) then svLis is quite happy for you to make any polynomial  you like out of them and use it as an inequality (wherever it's negative or zero is solid) to define a set: Heaven knows what sort of shape that would make--it doesn't matter; the point is that this is the basic mechanism in svLis for building curved solids.

How can this be used to solve one of the fundamental requirements of geometric modelling: that of forming a smooth transition or blend between two other shapes? Figure 11 illustrates what I mean. In it, two pipes  join at an angle to make an angled T-piece, but the join does not have a sharp corner. An extra surface has been unioned with the cylinders to round off the join. Almost all real engineering components need such blends  somewhere to avoid weak sharp corners in dies and moulds, to allow for machining by round cutters, for casting or moulding, to avoid problems with fluid flow, or simply to look nice.
 
 

\begin{figure}% latex2html id marker 1310\epsfysize=80mm\centerline{\epsffile... ...ctr}.A Liming blend between two cylinders.\end{minipage}\end{center}\end{figure}


 



The technique for solving this problem was developed in the early 1980s by John Woodwark, Andy Wallis and Alan Middleditch (see the Bibliography for details). It stems from a method for aircraft fuselage design invented by R.A. Liming  in 1944.

Suppose we have four linear functions:

\begin{displaymath}L_i = A_i x + B_i y + C_i, \hspace{5mm} i = 1 \ldots 4.\end{displaymath}


 


When set to 0, these define four lines in the plane (Figure 12). If we then form a quadratic $Q = \lambda L_1 L_2 - (1 - \lambda) L_3 L_4$ (where $\lambda$ is a constant between 0 and 1), the curve where the quadratic  is zero will pass through the four intersection points of the lines. As we change the value of $\lambda$ a whole family of quadratics will be generated (some ellipses, some hyperbolas) that all share this property.
 
 

\begin{figure}% latex2html id marker 1319\epsfysize=80mm\centerline{\epsffile... ...tr}.A Liming quadratic between four lines.\end{minipage}\end{center}\end{figure}


 



This will work in three dimensions just as well. SvLis can build a quadric  primitive that went through the four lines of intersection of those planes f1 to f4 like this:

The shape that this produced would depend on the orientation of the planes and the value of $\lambda$, but would probably be similar to an elliptical cone. Note, incidentally, that the signs are important, by which I mean that, even though L1 = 0 and -L1 = 0 define the same line, the resulting polynomials will be different.

Suppose now that we bring L3 and L4 together so that they coincide (that is, so that L3 = L4). The quadratic Q will now have the property that it passes through the intersections of L1 and L3, and of L2 and L3, in such a way that it is tangential to L1 and L2 at those points (Figure 13).
 
 

\begin{figure}% latex2html id marker 1328\epsfysize=80mm\centerline{\epsffile... ...o two lines where they are cut by a third.\end{minipage}\end{center}\end{figure}


 


Again this will work with planes as well, and the svLis code becomes:

The quadric q would be tangential  to p1 and p2 along the lines where they cut p3. The value of $\lambda$  controls the shape of the curve: values close to unity will cause it to go tight into the corner between p1 and p2, values close to zero will cause it to stay close to p3.

How does all this help with the blending problem? The answer lies in the fact that the tangency property of the Liming formulation is retained for any polynomials p1, p2, and p3[*]; they don't have to be planes.

Let's look at how the T-piece in Figure 11 was done. The stages are illustrated in Figure 14. First the two cylinders  were created. Then a cone was created to define where on the cylinders' surfaces the tangency was to happen. Then a blend surface was made using these three primitives and the Liming formula. Only part of this blend surface is needed, of course: the part inside the cone. This is trivial to obtain as all that needs to be done is to intersect the blend with the cone. Finally the resulting bit of blend is unioned with the two cylinders to make the blended T-piece.
 
 

\begin{figure}% latex2html id marker 1350\epsfxsize=55mm\centerline{\epsffile... ...e cone defining the tangency of the blend.\end{minipage}\end{center}\end{figure}


 


Here's the code:

This is a bit of a cheat, as the `pipes' don't have holes down them. To get more useful sorts of pipe you'd have to make a cavity inside as well, of course. Also, I haven't included the code that intersects the pipes with half-planes to give their flat ends, and to make cyl_2 stop part-way through cyl_1 so that it doesn't stick out on the other side.

As the axis of the smaller cylinder is the same line as the cone's axis, the point for the cone's vertex needs to be the origin of that line. Because the cylinders and the cone are quadrics (that is, they have polynomial degree 2), the blend polynomial blend_p has degree 4. This method of making fillets and smooth transitions is very versatile, because it starts with polynomials and produces polynomials. Thus it is possible to use it to make blends on blends. The only problem with this is that the degree of the blend polynomials rises rapidly, and can make the results intractable. However, this will only happen with quite esoteric models. For most ordinary solid objects, the technique is neat, quick, and convenient.

The effect of varying the value of $\lambda$  is shown in Figure 15.
 
 

\begin{figure}% latex2html id marker 1362\epsfysize=80mm\centerline{\epsffile... ...ue of $\lambda$\space in the Liming blend.\end{minipage}\end{center}\end{figure}


 


The Liming blending scheme can be used to smooth out any corner. However, many objects that svLis is required to model need another sort of blend as well. Consider the transition  from the body to the neck of a bottle. This is tangentially smooth where it joins the base surfaces, and goes from a large diameter to a smaller one. This kind of blend, a sort of varying-section tube or duct for connecting two objects that are (roughly) in-line, is the other kind that's needed. SvLis' solution to this problem was devised by Dayong Zhang.

Figure 16 is rather similar to Figure 13, except that here we generate a cubic, C, that is tangential to L1 where L3 crosses it, and tangential to L2 where L4 crosses it.
 
 

\begin{figure}% latex2html id marker 1371\epsfysize=80mm\centerline{\epsffile... ...ooth transition from $L_1$\space to $L_2$.\end{minipage}\end{center}\end{figure}


 



C has the form $(1 - \lambda) L_1 {L_4}^2 + \lambda L_2 {L_3}^2$.As you might expect, a cubic is needed because in general such blends need to have inflections in them. As with the Liming blend, the underlying shapes Li don't need to be linear polynomials. They can be polynomials of higher degree, or indeed can contain sines, exponentials, and so on (see the next section). Again, as with the Liming blend, the primitives that define where the blend is tangential to the base surfaces (L3 and L4) can also be used set-theoretically to chop the blend curve or surface to size to create the solid required.

Here is some code that takes two non-coaxial cylinders  and constructs a smooth transition from one to the other. The transition tangencies are deliberately set to be non-perpendicular to the cylinder axes in order to demonstrate the versatility of the technique.

Figure 17 shows the resulting shape.
 
 


\begin{figure}% latex2html id marker 1380\epsfysize=80mm\centerline{\epsffile... ... transition between two offsetcylinders. \end{minipage}\end{center}\end{figure}


 



Potentials and non-polynomials

All primitives generate potential values. This is to say that, if you have any point (x,y,z) in space, and you find the value of a primitive at that point[*], that value will be the primitive's potential at that point. The convention is, as we have seen, that a negative potential means solid, zero means surface, and positive potentials correspond to air.

In addition to doing arithmetic on planes to make complicated shapes, svLis will also allow you to use other functions. For example you can take the sine  of a primitive:


You could compress a lot of that code; I've written it out step by step for clarity. Figure 18 shows the shape that results. The primitive zp has a potential that is the same as the value of z everywhere; sx is the same as $\sin(x)$ everywhere. The effect of adding them is to make the surface of zp (where z is 0) undulate, because now the zero locations are wherever $z + \sin(x) = 0$.
 
 

\begin{figure}% latex2html id marker 1399\epsfysize=80mm\centerline{\epsffile... ...inusoidal solid intersected with a cuboid.\end{minipage}\end{center}\end{figure}


 




You can play lots of tricks like this. Experiment! The functions you have available are sin, cos  and exp. You'll note that there is no log , nor tan. Logarithms and tangents both behave very nastily for quite sensible arguments, like negative ones or ones close to $\pi/2$. SvLis would have to be riddled with special-case code to deal with these functions.

There is a further function available: the signed square root , s_sqrt. An ordinary square root would give imaginary answers (or errors) for the solid (negative) region of a primitive, of course, and so would not be a lot of use. The function s_sqrt applies the sign of a value to the square-root of the absolute value. This retains the idea of solid, surface, and air for primitives, but allows you to square-root their potential function. If you do this for spheres and cylinders, you will see that the potential function that you end up with will now return real tape-measure distance just as the potential function of a plane does. Also, given the ubiquitous nature of Pythagoras' theorem, you will find that--when you build your own primitives using arithmetic on planes and other primitives--their potential functions will often also be squared distance (or distance to the fourth power, or whatever); using s_sqrt can help you to linearize their potentials. As we shall see in the next chapter, there are functions available which will tell you the nearest primitive to a point in a model, measuring distance in terms of potential value. If the potentials are all real distances, this makes it easier to find the nearest surface, which is often a useful thing to know.

Finally in this section, there is a  sign function that returns +1 or -1 depending upon the sign of the primitive for which it is called. Note that this does not return 0 on the surface of a primitive, it returns -1.
 
 
 

Sheets and wires

SvLis is a geometric modeller, not just a solid modeller. Back on Page [*], I said that I'd explain the difference. Solid modellers can only represent three-dimensional solid objects; but geometric modellers can represent all sorts of other geometric entities as well. In particular, svLis can represent zero-thickness sheets, curved lines (or wires) in space, and points in space--as well as solids.

We have just seen how primitives have potential functions which are positive for air, zero for surface, and negative for solid. Clearly a sheet has no solid--it's just surface and air, or zero and positive. SvLis achieves this by providing you with an absolute-value function  that you can apply to a primitive. The result is a primitive that has no solid side; it is just surface and air. For example, suppose we have an ordinary solid sphere s, and the sinusoidal solid primitive wave from Page [*]. We could turn the wave into a sheet, make it into a set, and union it with the sphere:


\begin{figure}% latex2html id marker 1437\epsfxsize=55mm\centerline{\epsffile... ... and differenced from, a sinusoidal sheet.\end{minipage}\end{center}\end{figure}


 


Figure 19 shows the result, and also the result of doing a difference rather than a union. In this way you can cut holes in sheets, limit their boundaries with intersections, and treat them as sets just like a solid primitive.

What about wires? Well, to get a curving line in space, you just intersect two sheets. The result may be more than one curve, of course (think of a small cylindrical sheet cutting a big one, for example), but you can chop them about by differencing and intersecting solids with them, just as with sheets. Figure 20 shows two such sheet cylinders that have had a solid cuboid subtracted from where they cross (so you can see what's going on) and have then had their intersection unioned in to show the curved wires where they cross each other. Here is the code[*]:


\begin{figure}% latex2html id marker 1448\epsfxsize=80mm\centerline{\epsffile... ... sheets intersected to make curving wires.\end{minipage}\end{center}\end{figure}


 
 



The sv_primitive class

The sv_primitive class  is actually the most complicated class (in terms of quantity of data per member) in svLis. In this section I will describe a simplified version of it because it is typical of the way in which all svLis classes are defined and used. If you think you can stomach the full version, look in the svLis header files sv_b_cls.h and prim.h for all the inconvenient things that I gloss over here to make the explanation tractable.

When you use svLis you hardly ever have to mess around with pointers  to complicated classes like primitives, nor do you have to worry about storage allocation and de-allocation. SvLis handles all this for you in a very efficient way. 

The trick is the standard one of reference counting. The svLis implementation is extremely elegant but unfortunately I can claim almost no credit for it as I nicked it from Scott Meyers' excellent book--see the Bibliography. First we have to define a smart pointer--that is a pointer that knows when it gets copied. We'll need this to point to several different classes, so this is done as a template in the file sv_b_cls.h:

The class T has to provide two procedures: add_reference() and remove_reference(). In practice T will be a derived class for each svLis object (primitives in the example to follow); a class derived from the base class that handles the reference counting. The base class is called sv_refct, also defined in sv_b_cls.h: As can be seen, add_reference() just increments the reference count. Whenever any complicated svLis class like primitives gets copied the data stays in one place, only a pointer is copied, and the number of pointers pointing to the data is recorded in ref_count. When an item is deleted (for example when it goes out of scope) the reference count is decremented. Only when the reference count goes through 0 (and so nothing is pointing at the data) does the data actually get deleted.

Finally, here is the primitive class using the above two definitions. It has an internally defined struct called prim_data containing the actual data that is the thing pointed to, and a pointer to that struct that is all the information that individual primitives hold.

This reference-counting trick is used throughout svLis: not only for primitives, but also for sets, models, attributes and all sorts of other things that I haven't introduced yet.

There are two principal advantages to it.

Firstly, you--the user--can just assign things, return them from functions, incorporate them in other things and so on by referring to variables of the appropriate class in a natural a = b + c sort of way even if b and c take up five megabytes each. SvLis will handle all this efficiently without copying loads of data.

The second advantage is that everything that you build in svLis exists in only one place; there are no multiple copies. This makes the efficient implementation of things like undo (or rollback) functions trivial; if, for example, you union two enormously complicated sets, then decide that you didn't want to do that, you can just extract the originals like this:

This code[*] just copies a few pointers and increments and decrements the reference counts (although this fancy_set(...) function of yours may take ages to execute for all I know).


nextuppreviouscontentsindex
Next:Sets, models, and DIY Up:SvLis Introduction Previous:Fundamental geometric structures

Adrian Bowyer


PERFICTA  PERFRACTA  QVAERENDO  PERFECTA