This is 3DLDF.info, produced by makeinfo version 4.5 from 3DLDF.texi.

This is the 3DLDF User and Reference Manual, edition 1.1.5 for 3DLDF
1.1.5.  This manual was last updated on 14 January 2004.  3DLDF is a
GNU package for three-dimensional drawing with MetaPost output.  The
author is Laurence D. Finston.

   Copyright (C) 2003, 2004 Laurence D. Finston.

     Permission is granted to copy, distribute and/or modify this
     document under the terms of the GNU Free Documentation License,
     Version 1.2 or any later version published by the Free Software
     Foundation; with no Invariant Sections, no Front-Cover Texts, and
     no Back-Cover Texts.  A copy of the license is included in the
     section entitled "GNU Free Documentation License".
   
INFO-DIR-SECTION GNU packages
START-INFO-DIR-ENTRY
* 3DLDF: (3DLDF).            3D drawing with MetaPost output.
END-INFO-DIR-ENTRY


File: 3DLDF.info,  Node: Rotating Points,  Prev: Shearing Points,  Up: Transforming Points

Rotating
========

   The function `rotate()' rotates a `Point' about one or more of the
main axes.  It takes three `real' arguments, specifying the angles of
rotation in degrees about the x, y, and z-axes respectively.  Only the
first argument is required, the other two are 0 by default.  If
rotation about the y-axis, or the y and z-axes only are required, then 0
must be used as a placeholder for the first and possibly the second
argument.

     Point p(0, 1);
     p.rotate(90);
     p.show("p:");
     -| p: (0, 0, -1)
     p.rotate(0, 90);
     p.show("p:");
     -| p: (1, 0, 0)
     p.rotate(0, 0, 90);
     p.show("p:");
     -| p: (0, 1, 0)

   The rotations are performed successively about the x, y, and z-axes.
However, rotation is not a commutative operation, so if rotation about
the main axes in a different order is required, then `rotate()' must be
invoked more than once:

     Point A(2, 3, 4);
     Point B(A);
     A.rotate(30, 60, 90);
     A.show("A:");
     -| A: (-4.59808, -0.700962, 2.7141)
     B.rotate(0, 0, 90);
     B.rotate(0, 60);
     B.rotate(30);
     B.show("B:");
     -| B: (-4.9641, 1.43301, -1.51795)

   Rotation need not be about the main axes;  it can also be performed
about a line defined by two `Points'.  The function `rotate()' with two
`Point' arguments and a `real' argument for the angle of rotation (in
degrees) about the axis.  The `real' argument is optional, with 180
degrees

   as the default.

     Point p0 (-1.06066, 0, 1.06066);
     Point p1 (1.06066, 0, -1.06066);
     p1 *= p0.rotate(0, 30, 30);
     p0.show("p0:");
     -| p0: (-1.25477, -0.724444, 0.388228)
     p1.show("p1:");
     -| p1: (1.25477, 0.724444, -0.388228)
     p0.draw(p1);
     Point p2(1.06066, 0, 1.06066);
     p2.show("p2:");
     -| p2: (1.06066, 0, 1.06066)
     Point p3(p2);
     p3.rotate(p1, p0, 45);
     p3.show("p3:");
     -| p3 (1.09721, 1.15036, 1.17879)
     Point p4(p2);
     p4.rotate(p1, p0, 90);
     p4.show("p4:");
     -| p4: (0.882625, 2.05122, 0.485242)
     Point p5(p2);
     p5.rotate(p1, p0, 135);
     p5.show("p5:");
     -| p5: (0.542606, 2.17488, -0.613716)
     Point p6(p2);
     p6.rotate(p1, p0);
     p6.show("p6:");
     -| p6: (0.276332, 1.44889, -1.47433)

   [Figure 2 not displayed.]

   I have sometimes gotten erroneous results using `rotate()' for
rotation about two `Points'.  It's usually worked to reverse the order
of the `Point' arguments, or to change sign of the angle argument.  I
think I've fixed the problem, though.


File: 3DLDF.info,  Node: Transforms,  Next: Drawing and Labeling Points,  Prev: Transforming Points,  Up: Top

Transforms
**********

   When `Points' are transformed using `shift()', `shear()', or one of
the other transformation functions, the `world_coordinates' are not
modified directly.  Instead, another data member of `class Point' is
used to store the information about the transformation, namely
`transform' of type `class Transform'.  A `Transform' object has a
single data element of type `Matrix' and a number of member functions.
A `Matrix' is simply a 4 X 4 array(1) of `reals' defined using `typedef
real Matrix[4][4]'.  Such a matrix suffices for performing all of the
transformations (affine and perspective) possible in three-dimensional
space.(2) Any combination of transformations can be represented by a
single transformation matrix.  This means that consecutive
transformations of a `Point' can be "saved up" and applied to its
coordinates all at once when needed, rather than updating them for each
transformation.

   `Transforms' work by performing matrix multiplication of `Matrix'
with the homogeneous `world_coordinates' of `Points'.  If a set of
homogeneous coordinates \alpha = (x, y, z, w) and
     `Matrix M' =
     a e i m
     b f j n
     c g k o
     d h l p
   then the set of homogeneous coordinates \beta resulting from
multiplying \alpha and M is calculated as follows:
     \beta = \alpha\times M = ((xa + yb + zc + wd),  (xe + yf + zg + wh),
     (xi + yj + zk + wl), (xm + yn + zo + wp))
   Please note that each coordinate of \beta can be influenced by all
of the coordinates of \alpha.

   Operations on matrices are very important in computer graphics
applications and are described in many books about computer graphics
and geometry.  For 3DLDF, I've mostly used Huw Jones' `Computer
Graphics through Key Mathematics' and David Salomon's `Computer
Graphics and Geometric Modeling'.

   It is often useful to declare and use `Transform' objects in 3DLDF,
just as it is for `transforms' in Metafont.  Transformations can be
stored in `Transforms' and then be used to transform `Points' by means
of `Point::operator*=(const Transform&)'.

     1. Transform t;
     2. t.shift(0, 1);
     3. Point p(1, 0, 0);
     4. p *= t;
     5. p.show("p:");
     -| p: (1, 1, 0)

   When a `Transform' is declared (line 1), it is initialized to an
"identity matrix".  All identity matrices are square, all of the
elements of the main diagonal (upper left to lower right) are 1, and
all of the other elements are 0.  So a 4 X 4 identity matrix, as used
in 3DLDF, looks like this:
     1 0 0 0
     0 1 0 0
     0 0 1 0
     0 0 0 1
   If a matrix A is multiplied with an identity matrix I, the result is
identical to A, i.e., A * I = A.  This is the salient property of an
identity matrix.

   The same affine transformations are applied in the same way to
`Transforms' as they are to `Points', i.e., the functions `scale()',
`shift()', `shear()', and `rotate()' correspond to the `Point' versions
of these functions, and they take the same arguments:

     Point p;
     Transform t;
     p.shift(3, 4, 5);
     t.shift(3, 4, 5);
     => p.transform == t
     p.show_transform("p:");
     -| p:
        Transform:
              0   0.707   0.707       0
         -0.866   0.354  -0.354       0
           -0.5  -0.612   0.612       0
              0       0       0       1
     t.show("t:");
     -| t:
              0   0.707   0.707       0
         -0.866   0.354  -0.354       0
           -0.5  -0.612   0.612       0
              0       0       0       1

* Menu:

* Applying Transforms to Points Intro::
* Inverting Transforms::

   ---------- Footnotes ----------

   (1) It is unfortunate that the terms "array", "matrix", and "vector"
have different meanings in C++  and in normal mathematical usage.
However, in practice, these discrepancies turn out not to cause many
problems.  Stroustrup, `The C++  Programming Language', section  22.4,
p. 662.

   (2) In fact, none of the operations for transformations require all
of the elements of a 4 X 4 matrix.  In many 3D graphics programs, the
matrix operations are modified to use smaller transformation matrices,
which reduces the storage requirements of the program.  This is a bit
tricky, because the affine transformations and the perspective
transformation use different elements of the matrix.  I consider that
the risk of something going wrong, possibly producing hard-to-find
bugs, outweighs any benefits from saving memory (which is usually no
longer at a premium, anyway).  In addition, there may be some
interesting non-affine transformations that would be worth
implementing.  Therefore, I've decided to use full 4 X 4 matrices in
3DLDF.


File: 3DLDF.info,  Node: Applying Transforms to Points Intro,  Next: Inverting Transforms,  Prev: Transforms,  Up: Transforms

Applying Transforms to Points
=============================

   A `Transform' t is applied to a `Point' P using the binary `*='
operation (`Point::operator*=(const Transform&)') which performs matrix
multiplication of `P.transform' by `t'.  *Note Point Reference;
Operators: Point Operators.

     Point P(0, 1);
     Transform t;
     t.rotate(90);
     t.show("t:");
     -| t:
        1       0       0       0
        0       0      -1       0
        0       1       0       0
        0       0       0       1
     P *= t;
     P.show_transform("P:");
     -| P:
     Transform:
        1       0       0       0
        0       0      -1       0
        0       1       0       0
        0       0       0       1
     P.show("P:");
     -| P: (0, 0, -1)

   In the example above, there is no real need to use a `Transform',
since `P.rotate(90)' could  have been called directly.  As
constructions become more complex, the power of `Transforms' becomes
clear:

     1. Point p0(0, 0, 0);
     2. Point p1(10, 5, 10);
     3. Point p2(16, 14, 32);
     4. Point p3(25, 50, 99);
     5. Point p4(12, 6, 88);
     6. Transform a;
     7. a.shift(2, 3, 4);
     8. a.scale(1, 3, 1);
     9. p2 *= p3 *= a;
     10. a.rotate(p0, p1, 75);
     11. p4 *= a;
     12. p2.show("p2:");
        -| p2: (18, 51, 36)
     13. p3.show("p3:");
        -| p3: (27, 159, 103)
     14. p4.show("p4:");
        -| p4: (24.4647, -46.2869, 81.5353)

   In this example, a is shifted and scaled, and a is applied to both
in line 9.  This works, because the binary operation `operator*=(const
Transform& t)' returns T, making it possible to chain invocations of
`*='.  Following this, a is rotated 75 degrees

   about the line through p_0 and p_1.  Finally, all three
transformations, which are stored in a, are applied to p_4.


File: 3DLDF.info,  Node: Inverting Transforms,  Prev: Applying Transforms to Points Intro,  Up: Transforms

Inverting Transforms
====================

   "Inversion" is another operation that can be performed on
`Transforms'.  This makes it possible to reverse the effect of a
`Transform', which may represent multiple transformations.

     Point p;
     Transform t;
     t.shift(1, 2, 3);
     t.scale(2, 3, 4);
     t.rotate(45, 45, 30);
     t.show("t:");
     -| t:
        1.22   0.707    1.41       0
       0.238    2.59    -1.5       0
       -3.15    1.45       2       0
       -7.74    10.2    4.41       1
     p *= t;
     p.show("p:");
     -| p: (-7.74, 10.2, 4.41)
     Transform u;
     u = t.inverse();
     u.show("u:");
     -| u:
       0.306  0.0265  -0.197 2.85e-09
       0.177   0.287  0.0906 -1.12e-09
       0.354  -0.167   0.125       0
          -1      -2      -3       1
     p *= u;
     p.show("p:");
     -| p: (0, 0, 0)
     u *= t;
     u.show("u:");
     -| u:
           1       0       0       0
           0       1       0       0
           0       0       1       0
           0       0       0       1

   If `inverse()' is called with no argument, or with the argument
`false', it returns a `Transform' representing its inverse, and remains
unchanged.  If it is called with the argument `true', it is set to its
inverse.

   Complete reversal of the transformations applied to a `Point', as in
the previous example, probably won't make much sense.  However, partial
reversal is a valuable technique.  For example, it is used in
`rotate()' for rotation about a line defined by two `Points'.  The
following example merely demonstrates the basic principle;  an example
that does something useful would be too complicated.

     Transform t;
     t.shift(3, 4, 5);
     t.rotate(45);
     t.scale(2, 2, 2);
     Point p;
     p *= t;
     p.show("p:");
     -| p: (6, 12.7279, 1.41421)
     t.inverse(true);
     p.rotate(90, 90);
     p *= t;
     p.show("p:");
     -| p: (3.36396, -5.62132, -2.37868)


File: 3DLDF.info,  Node: Drawing and Labeling Points,  Next: Paths,  Prev: Transforms,  Up: Top

Drawing and Labeling Points
***************************

* Menu:

* Drawing Points Intro::
* Labeling Points Intro::


File: 3DLDF.info,  Node: Drawing Points Intro,  Next: Labeling Points Intro,  Prev: Drawing and Labeling Points,  Up: Drawing and Labeling Points

Drawing Points
==============

   It's all very well to declare `Points', place them at particular
locations, print their locations to standard output, and transform them,
but none of these operations produce any MetaPost output.  In order to
do this, the first step is to use "drawing and filling commands".  The
drawing and filling commands in 3DLDF are modelled on those in Metafont.

   The following example demonstrates how to draw a dot specifying a
`Color' (*note Color Reference::) and a "pen"(1).

     Point P(0, 1);
     P.drawdot(Colors::black, "pencircle scaled 3mm");

   [Figure 3 not displayed.]

   In `drawdot()', a `Color' argument precedes the `string' argument
for the pen, so "`Colors::black'" must be specified as a placeholder in
the call to `drawdot()'.(2)

   The following example "undraws" a dot at the same location using a
smaller pen. `undraw()' does not take a `Color' argument.

     p.undrawdot("pencircle scaled 2mm");

   [Figure 4 not displayed.]

For complete descriptions of `drawdot()' and `undrawdot()', see *Note
Point Reference; Drawing: Point Drawing Functions.

   Drawing and undrawing dots is not very exciting.  In order to make a
proper drawing it is necessary to _connect_ the `Points'.  The most
basic way of doing this is to use the `Point' member function `draw()'
with a `Point' argument:

     Point p0;
     Point p1(2, 2);
     p0.draw(p1);

   [Figure 5 not displayed.]

`p0.draw(p1)' is equivalent in its effect to `p1.draw(p0)'.

   The function `Point::draw()' takes a required `Point&' argument (a
"reference"(3) to a `Point') an optional `Color' argument, and optional
`string' arguments for the dash pattern and the pen.  The `string'
arguments, if present, are passed unchanged to the output file.  The
empty `string' following the argument `p1' is a placeholder for the
dash pattern argument, which isn't used here.

     p0.draw(p1, Colors::gray, "", "pensquare scaled .5cm rotated 45");

   [Figure 6 not displayed.]

   The function `Point::undraw()' takes a required `Point&' argument and
optional `string' arguments for the dash pattern and the pen.  Unlike
`Point::draw()', a `Color' argument would have no meaning for
`Point::undraw()'.  The `string' arguments are passed unchanged to the
output file.

   `undraw()' can be used to "hollow out" the region drawn in [the
previous figure] .  Since a dash pattern is used, portions of the
middle of the region are not undrawn.

     p0.undraw(p1, "evenly scaled 6", "pencircle scaled .2cm");

   [Figure 7 not displayed.]

   For complete descriptions of `draw()' and `undraw()', see *Note
Point Reference; Drawing: Point Drawing Functions.

   ---------- Footnotes ----------

   (1) Pens are a concept from Metafont.  In 3DLDF, there is currently
no type "`Pen'".  Pen arguments to functions are simply `strings', and
are written unaltered to `out_stream'.  For more information about
Metafont's `pens', see Knuth, `The Metafontbook', Chapter 4.

   (2) `Colors' are declared in the namespace `Colors', so if you have
a "`using'" declaration in the function where you use `drawdot()', you
can write "`black'" instead of "`Colors::black'".  For more information
about namespaces, see Stroustrup,  `The C++  Programming Language',
Chapter 8.

   (3) "A "reference" is an alternative name for an object.  The main
use of references is for specifying arguments and return values for
functions in general and for overloaded operators (Chapter 11) in
particular."  Stroustrup, `The C++  Programming Language', section 5.5,
p. 97.


File: 3DLDF.info,  Node: Labeling Points Intro,  Prev: Drawing Points Intro,  Up: Drawing and Labeling Points

Labeling Points
===============

   The labels in the previous examples were made by using the functions
`Point::label()' and `Point::dotlabel()', which make it possible to
include TeX text in a drawing.

   `label()' and `dotlabel()' take `string' arguments for the text of
the label and the position of the label with respect to the `Point'.
The label text is formatted using TeX, so it can contain math mode
material between dollar signs.  Please note that double backslashes
must be used, where a single backslash would suffice in a file of
MetaPost code, for example, for TeX control sequences.  Alternatively,
a `short' argument can be used for the label.

   The position argument is optional, with `"top"' as the default.  If
the empty `string' `""' is used, the label will centered about the
`Point' itself.  This will usually only make sense for `label()',
because it would otherwise interfere with the dot.  Valid arguments for
the position are the same as in MetaPost: `"top"', `"bot"' (bottom),
`"lft"' (left), `"rt"' (right), `"ulft"' (upper left), `"urt"' (upper
right), `"llft"' (lower left), and `"lrt"' (lower right).

     Point p0;
     Point p1(1);
     Point p2(2);
     Point p3(p0);
     Point p4(p1);
     Point p5(p2);
     p3 *= p4 *= p5.shift(0, 1);
     p0.draw(p1);
     p1.draw(p2);
     p2.draw(p5);
     p5.draw(p4);
     p4.draw(p3);
     p3.draw(p0);
     p0.label($p_0$, "");
     p1.dotlabel(1);
     p2.dotlabel("p2", "bot");
     p3.dotlabel("This is $p_3$", "lft");
     p4.label(4);
     p5.label("$\\leftarrow p_5$", "rt");

   [Figure 8 not displayed.]

   For complete descriptions of `Point::label()' and
`Point::dotlabel()', see *Note Points; Labelling: Labelling Points.


File: 3DLDF.info,  Node: Paths,  Next: Plane Figures,  Prev: Drawing and Labeling Points,  Up: Top

Paths
*****

   `Points' alone are not enough for making useful drawings.  The next
step is to combine them into `Paths', which are similar to Metafont's
paths, except that they are three-dimensional.  A `Path' consists of a
number of `Points' and `strings' representing the "connectors".  The
latter are not processed by 3DLDF, but are passed unchanged to the
output file.  They must be valid connectors for MetaPost, e.g.:

     ..
     ...
     --
     ---
     &
     curl{2}..
     {dir 60}..
     {z1 - z2}..
     .. tension 1 and 1.5..
     ..controls z1 and z2..

   Usually, it will only make sense to use .. or -, and not ..., --,
tension, curl, controls, or any of the other possibilities, in `Paths',
unless you are sure that they will only be viewed with no
foreshortening due to the perspective projection.  This can be the
case, when a `Path' lies in a plane parallel to one of the major
planes, and is projected using parallel projection onto that plane.
Otherwise, the result of using these connectors is likely to be
unsatisfactory, because MetaPost performs its calculations based purely
on the two-dimensional values of the points in the perspective
projection.  While the `Points' on the `Path' will be projected
correctly, the course of the `Path' between these `Points' is likely to
differ, depending on the values of the `Focus' used (*note Focuses:
Focuses Getstart.), so that different views of the same `Path' may well
be mutually inconsistent.  This problem doesn't arise with "-", since
the perspective projection does not "unstraighten" straight lines, but
it does with "..", even without tension, curl, or controls.  The
solution is to use enough `Points', since a greater number of `Points'
on a `Path' tends to reduce the number of possible courses through the
`Points'.(1)

* Menu:

* Declaring and Initializing Paths::
* Drawing and Filling Paths Intro::

   ---------- Footnotes ----------

   (1) I believe that counter-examples could probably constructed, but
for the most common cases, the principle applies.


File: 3DLDF.info,  Node: Declaring and Initializing Paths,  Next: Drawing and Filling Paths Intro,  Prev: Paths,  Up: Paths

Declaring and Initializing Paths
================================

   There are various ways of declaring and initializing `Paths'.  The
simplest is to use the constructor taking two `Point' arguments:

     Point A;
     Point B(2, 2);
     Path p(A, B);
     p.draw();

   [Figure 9 not displayed.]

   `Paths' created in this way are important, because they are
guaranteed to be linear, as long as no operations are performed on them
that cause them to become non-linear.  Linear `Paths' can be used to
find "intersections".  *Note Path Intersections::.

   `Paths' can be declared and initialized using a single connector and
an arbitrary number of `Points'.  The first argument is a `string'
specifying the connector.  It is followed by a `bool', indicating
whether the `Path' is cyclical or not. Then, an arbitrary number of
pointers to `Point' follow.  The last argument must be 0.(1)

     Point p[3];
     p[0].shift(1);
     p[1].set(1, 2, 2);
     p[2].set(1, 0, 2);
     Path pa("--", true, &p[0], &p[1], &p[2], 0);
     pa.draw();

   [Figure 10 not displayed.]

   Another constructor must be used for `Paths' with more than one
connector and an arbitrary number of `Points'.  The argument list
starts with a pointer to `Point', followed by `string' for the first
connector.  Then, pointer to `Point' arguments alternate with `string'
arguments for the connectors.  Again, the list of arguments ends in 0.
There is no need for a `bool' to indicate whether the `Path' is cyclical
or not;  if it is, the last non-zero argument will be a connector,
otherwise, it will be a pointer to `Point'.

     Point p[8];
     p[0].set(-2);
     p[1].set(2);
     p[2].set(0, 0, -2);
     p[3].set(0, 0, 2);
     p[4] = p[0].mediate(p[2]);
     p[5] = p[2].mediate(p[1]);
     p[6] = p[1].mediate(p[3]);
     p[7] = p[3].mediate(p[0]);
     p[4] *= p[5] *= p[6] *= p[7].shift(0, 1);
     Path pa(&p[0], "..", &p[4], "...", &p[2],
             "..", &p[5], "...", &p[1], "..", &p[6],
             "...", &p[3], "..", &p[7], "...", 0);
     pa.draw();

   [Figure 11 not displayed.]

   As mentioned above (*note Accuracy::), specifying connectors is
problematic for three-dimensional `Paths', because MetaPost ultimately
calculates the "most pleasing curve" based on the two-dimensional
points in the MetaPost code written by 3DLDF.(2) For this reason, it's
advisable to avoid specifying `curl', `dir', `tension' or control
points in connectors.  The more `Points' a (3DLDF) `Path' or other
object contains, the less freedom MetaPost has to determine the
(MetaPost) `path' through them.  So a three-dimensional `Path' or other
object in 3DLDF should have enough `Points' to ensure satisfactory
results.  The `Path' in [the previous figure]  does not really have
enough `Points'.  It may require some trial and error to determine what
a sufficient number of `Points' is in a given case.

   `Paths' are very flexible, but not always convenient.  3DLDF
provides a number of classes representing common geometric `Shapes',
which will be described in subsequent sections, and I intend to add
more in the course of time.

   ---------- Footnotes ----------

   (1) It's easy to forget to use `Point*' arguments, rather than plain
`Point' arguments, and to forget to end the list of arguments with 0.
If plain `Point' arguments are used, compilation fails with GCC.  With
the DEC compiler, compilation succeeds, but a memory fault error occurs
at run-time.  If the argument list doesn't end in 0, neither compiler
signals an error, but a memory fault error always occurs at run-time.

   (2) Knuth, `The METAFONTbook', Chapter 14, p. 127.


File: 3DLDF.info,  Node: Drawing and Filling Paths Intro,  Prev: Declaring and Initializing Paths,  Up: Paths

Drawing and Filling Paths
=========================

   The easiest way to draw a `Path' is with no arguments.

     Point pt[5];
     pt[0].set(-1, -2);
     pt[1].set(0, -3);
     pt[2].set(1, 0);
     pt[3].set(2, 1);
     pt[4].set(-1, 2);
     Path pa("..", true, &pt[0], &pt[1], &pt[2], &pt[3], &pt[4], 0);
     pa.draw();

   [Figure 12 not displayed.]

   Since `pa' is closed, it can be filled as well as drawn.  The
following example uses `fill()' with a `Color' argument, in order to
avoid having a large splotch of black on the page.  Common `Colors' are
declared in the `namespace Colors'.  *Note Color Reference::.

     pa.fill(Colors::gray);

   [Figure 13 not displayed.]

   Closed `Paths' can be filled and drawn, using the function
`filldraw()'.  This function draws the `Path' using the pen specified,
or MetaPost's `currentpen' by default.  A `Color' for drawing the
`Path' can also be specified, otherwise, the default color (currently
`Colors::black') is used.  In addition, the `Path' is filled using a
second `Color', which can be specified, or the background_color
(`Colors::background_color'), by default.  Filling a `Path' using the
background color causes it to hide objects that lie behind it.  *Note
Surface Hiding::, for a description of the surface hiding algorithm,
and examples.  Currently, this algorithm is quite primitive and only
works for simple cases.

     Point p0(-3, 0, 1);
     Point p1(3, 1, 1);
     p0.draw(p1);
     pa.filldraw();

   [Figure 14 not displayed.]

   The following example uses arguments for the `Colors' used for
drawing and filling, and the pen.  The empty string argument before the
pen argument is a placeholder for the dash pattern argument.

     pa.filldraw(black, gray, "",
        "pensquare xscaled 3mm yscaled 1mm rotated 60");

   [Figure 15 not displayed.]

   `Paths' can also be "undrawn", "unfilled", and "unfilldrawn", using
the corresponding functions:

     pa.fill(gray);
     p0.undraw(p1, "", "pencircle scaled 3mm");

   [Figure 16 not displayed.]

     pa.fill(gray);
     Path q;
     q = pa;
     q.scale(.5, .5);
     q.unfill();

   [Figure 17 not displayed.]

   The function `unfilldraw()' takes a `Color' argument for drawing the
`Path', which is `*Colors::background_color' by default.  This makes it
possible to unfill the `Path' while drawing the outline with a visible
`Color'.  On the other hand, it also makes it necessary to specify
`*Colors::background_color' or `Colors::white', if the user wants to
use the dash pattern and/or pen arguments, without drawing the `Path'.

     pa.fill(gray);
     q.unfilldraw(white, "", "pensquare xscaled 3mm yscaled 1mm");

   [Figure 18 not displayed.]

   The following example demonstrates the use of `unfilldraw()' with
`black' as its `Color' argument.  Unfortunately, it also demonstrates
one of the limitations of the surface hiding algorith:  The line from
`p0' to `p1' is hidden by the filled `Path pa'.  Since the portion of
`pa' covered by `Path q' has been unfilled, the line from p_0 to p_1
should be visible as it passes through `q'.  However, from the point of
view of 3DLDF, there is no relationship between `pa' and `q';  nor does
it "know" whether a `Path' has been filled or unfilled.  If it's on a
`Picture', it will hide objects lying behind it, unless the surface
hiding algorithm fails for another reason.  *Note Surface Hiding::, for
more information.

     p0.draw(p1);
     pa.fill(gray);
     q.unfilldraw(black, "", "pensquare xscaled 3mm yscaled 1mm");

   [Figure 19 not displayed.]

   *Note Paths; Drawing and Filling: Drawing and Filling Paths, for more
information, and complete descriptions of the functions.


File: 3DLDF.info,  Node: Plane Figures,  Next: Solid Figures,  Prev: Paths,  Up: Top

Plane Figures
*************

   3DLDF currently includes the following classes representing plane
geometric figures:  `Polygon', `Reg_Cl_Plane_Curve' ("Regular Closed
Plane Curve"), `Reg_Polygon' ("Regular Polygon"), `Rectangle',
`Ellipse' and `Circle'.  `Polygon' and `Reg_Cl_Plane_Curve' are derived
from `Path', `Reg_Polygon' and `Rectangle' are derived from `Polygon',
and `Ellipse' and `Circle' are derived from `Reg_Cl_Plane_Curve'.
`Polygon' and `Reg_Cl_Plane_Curve' are meant to be used as base classes
only, so objects of these types should normally never be declared.

   Since `Reg_Polygon', `Rectangle', `Ellipse', and `Circle' all
ultimately derive from `Path', they are really just special kinds of
`Path'.  In particular, they inherit their drawing and filling
functions from `Path', and their transformation functions take the same
arguments as the `Path' versions.  They also have constructors and
setting functions that work in a similar way, with a few minor
differences, to account for their different natures.  See *Note Polygon
Reference::, *Note Rectangle Reference::, *Note Ellipse Reference::,
and *Note Circle Reference::, for complete information on these classes.

* Menu:

* Regular Polygons Getstart::
* Rectangles Getstart::
* Ellipses Getstart::
* Circles Getstart::


File: 3DLDF.info,  Node: Regular Polygons Getstart,  Next: Rectangles Getstart,  Prev: Plane Figures,  Up: Plane Figures

Regular Polygons
================

   The following example creates a pentagon in the x-z plane, centered
about the origin, whose enclosing circle has a radius equal to 3cm.

     default_focus.set(2, 3, -10, 2, 3, 10, 10);
     Reg_Polygon p(origin, 5, 3);
     p.draw();

   [Figure 20 not displayed.]

   Three additional arguments cause the pentagon to be rotated about
the x, y, and z axes by the amount indicated.  In this example, it's
rotated 90 degrees

   about the x-axis, so that it comes to lie in the x-y plane:

     Reg_Polygon p(origin, 5, 3, 90);
     p.draw();

   [Figure 21 not displayed.]

   In this example, it's rotated 36 degrees

   about the y-axis, so that it appears to point in the opposite
direction from the first example:
     Reg_Polygon p(origin, 5, 3, 0, 36);
     p.draw();

   [Figure 22 not displayed.]

   In this example, it's rotated 90 degrees

   about the z-axis, so that it lies in the z-y plane:

     Reg_Polygon p(origin, 5, 3, 0, 0, 90);
     p.draw();

   [Figure 23 not displayed.]

   In this example, it's rotated 45 degrees

   about the x, y, and z-axes in that order:

     Reg_Polygon p(origin, 5, 3, 45, 45, 45);
     p.draw();

   [Figure 24 not displayed.]

   `Reg_Polygons' need not be centered about the `origin'.  If another
`Point' PT is used as the first argument, the `Reg_Polygon' is first
created with its center at the origin, then the specified rotations, if
any, are performed.  Finally, the `Reg_Polygon' is shifted such that
its center comes to lie on PT:

     Point P(-2, 1, 1);
     Reg_Polygon hex(P, 6, 4, 60, 30, 30);
     hex.draw();

   [Figure 25 not displayed.]

   In the following example, the `Reg_Polygon' `polygon' is first
declared using the default constructor, which creates an empty
`Reg_Polygon'.  Then, the `polygon' is repeatedly changed using the
setting function corresponding to the constructor used in the previous
examples.  [next figure]  demonstrates that a given `Reg_Polygon' need
not always have the same number of sides.

     Point p(0, -3);
     Reg_Polygon polygon;
     for (int i = 3; i < 9; ++i)
       {
         polygon.set(p, i, 3);
         polygon.draw();
         p.shift(0, 1);
       }

   [Figure 26 not displayed.]


File: 3DLDF.info,  Node: Rectangles Getstart,  Next: Ellipses Getstart,  Prev: Regular Polygons Getstart,  Up: Plane Figures

Rectangles
==========

   A `Rectangle' can be constructed in the x-z plane by specifying a
center `Point', the width, and the height:

     Rectangle r(origin, 2, 3);
     r.draw();

   [Figure 27 not displayed.]

   Three additional arguments can be used to specify rotation about the
x, y, and z-axes respectively:

     Rectangle r(origin, 2, 3, 30, 45, 15);
     r.draw();

   [Figure 28 not displayed.]

   If a `Point' P other than the origin is specified as the center of
the `Rectangle', the latter is first created in the x-z plane, centered
about the origin, as above.  Then, any rotations specified are
performed.  Finally, the `Rectangle' is shifted such that its center
comes to lie at P:

     Point p0(.5, 1, 3);
     Rectangle r(p0, 4, 2, 30, 30, 30);
     r.draw();

   [Figure 29 not displayed.]

   This constructor has a corresponding setting function:

     Rectangle r;
     for (int i = 0; i < 180; i += 30)
       {
         r.set(origin, 4, 2, i);
         r.draw();
       }

   [Figure 30 not displayed.]

   `Rectangles' can also be specified using four `Points' as arguments,
whereby they must be ordered so that they are contiguous in the
resulting `Rectangle':

     Point pt[4];
     pt[0].shift(-1, -2);
     pt[2] = pt[1] = pt[0];
     pt[1].rotate(180);
     pt[3] = pt[1];
     pt[2] *= pt[3].rotate(0, 180);
     Rectangle r(pt[0], pt[2], pt[3], pt[1]);
     r.draw();

   [Figure 31 not displayed.]

   This constructor checks whether the `Point' arguments are coplanar,
however, it does not check whether they are really the corners of a
valid rectangle;  the user, or the code that calls this function, must
ensure that they are.  In the following example, `r', although not
rectangular, is a `Rectangle', as far as 3DLDF is concerned:

     pt[0].shift(0, -1);
     pt[3].shift(0, 1);
     Rectangle q(pt[0], pt[2], pt[3], pt[1]);
     q.draw();

   [Figure 32 not displayed.]

   This constructor is not really intended to be used directly, but
should mostly be called from within other functions, that should ensure
that the arguments produce a rectangular `Rectangle'.  There is also no
guarantee that transformations or other functions called on
`Rectangle', `Circle', or other classes representing geometric figures
won't cause them to become non-rectangular, non-circular, or otherwise
irregular.  Sometimes, this might even be desirable.  I plan to add the
function `Rectangle::is_rectangular()' soon, so that users can test
`Rectangles' for rectangularity.


File: 3DLDF.info,  Node: Ellipses Getstart,  Next: Circles Getstart,  Prev: Rectangles Getstart,  Up: Plane Figures

Ellipses
========

   `Ellipse' has a constructor similar to those for `Reg_Polygon' and
`Rectangle'.  The first argument is the center of the `Ellipse', and
the following two specify the lengths of the horizontal and vertical
axes respectively.  The `Ellipse' is first created in the x-z plane,
centered about the origin.  The horizontal axis lies along the x-axis
and the vertical axis lies along the z-axis.  The three subsequent
arguments specify the amounts of rotation about the x, y, and z-axes
respectively and default to 0.  Finally, `Ellipse' is shifted such that
its center comes to lie at the `Point' specified in the first argument.

     Point pt(-1, 1, 1);
     Ellipse e(pt, 3, 6, 90);
     e.draw();

   [Figure 33 not displayed.]

   As you may expect, this constructor has a corresponding setting
function:

     Ellipse e;
     real h_save = 1.5;
     real v_save = 2;
     real h = h_save;
     real v = v_save;
     Point p(-1);
     for (int i = 0; i < 5; ++i)
       {
           e.set(p, h, v, 90);
           e.draw();
           h_save += .25;
           v_save += .25;
           h *= sqrt(h_save);
           v *= sqrt(v_save);
           p.shift(0, 0, 2);
       }

   [Figure 34 not displayed.]


File: 3DLDF.info,  Node: Circles Getstart,  Prev: Ellipses Getstart,  Up: Plane Figures

Circles
=======

   `Circles' are constructed just like `Ellipses', except that the
vertical and horizontal axes are per definition the same, so there's
only one argument for the diameter, instead of two for the horizontal
and vertical axes:

     Point P(0, 2, 1);
     Circle c(P, 3.5, 90, 90);
     c.draw();

   [Figure 35 not displayed.]

   This constructor, too, has a corresponding setting function:

     Circle c;
     Point p(-1, 0, 5);
     for (int i = 0; i < 16; ++i)
       {
           c.set(p, 5, i * 22.5, 0, 0, 64);
           c.draw();
       }

   [Figure 36 not displayed.]

   In the preceding example, the last argument to `set()', namely "64",
is for the number of `Points' used for constructing the perimeter of
the `Circle'.  The default value is 16, however, if it is used,
foreshortening distorts the most nearly horizontal `Circle'.
Increasing the number of points used improves its appearance.  However,
there may be a limit to how much improvement is possible.  *Note
Accuracy::.


File: 3DLDF.info,  Node: Solid Figures,  Next: Pictures,  Prev: Plane Figures,  Up: Top

Solid Figures
*************

* Menu:

* Cuboid Getstart::
* Polyhedron Getstart::


File: 3DLDF.info,  Node: Cuboid Getstart,  Next: Polyhedron Getstart,  Prev: Solid Figures,  Up: Solid Figures

Cuboids
=======

   A "cuboid" is a solid figure consisting of six rectangular faces
that meet at right angles.  A cube is a special form of cuboid, whose
faces are all squares.  The constructor for the class `Cuboid' follows
the pattern familiar from the constructors for the plane figures:  The
first argument is the center of the `Cuboid', followed by three `real'
arguments for the height, width, and depth, and then three more `real'
arguments for the angles of rotation about the x, y, and z-axes.  The
`Cuboid' is first constructed with its center at the origin.  Its
width, height, and depth are measured along the x, y, and z-axes
respectively.  If rotations are specified, it is rotated about the x,
y, z-axes in that order.  Finally, it is shifted such that its center
comes to lie on its `Point' argument, if the latter is not the origin.

   If the width, height, and depth arguments are equal, the `Cuboid' is
a cube:

     Cuboid c0(origin, 3, 3, 3, 0, 30);
     c0.draw();

   [Figure 37 not displayed.]

   In the following example, the `Cuboid' is "filldrawn", so that the
lines dilineating the hidden surfaces of the `Cuboid' are covered.

     Cuboid c1(origin, 3, 4, 5, 0, 30);
     c1.filldraw();

   [Figure 38 not displayed.]


File: 3DLDF.info,  Node: Polyhedron Getstart,  Prev: Cuboid Getstart,  Up: Solid Figures

Polyhedron
==========

   The `class Polyhedron' is meant for use only as a base class; no
objects of type `Polyhedron' should be declared.  Instead, there is a
class for each of the different drawable polyhedra.  Currently, 3DLDF
defines only three: `Tetrahedron', `Dodecahedron', and `Icosahedron'.
There's no need for a `Cube' class, because cubes can be drawn using
`Cuboid' (*note Cuboid Getstart::).

   Polyhedra have a high priority in my plans for 3DLDF.  I intend to
add `Octahedron' soon, which will complete the set of regular Platonic
polyhedra.  Then I will begin adding the semi-regular Archimedean
polyhedra, and their duals.

   The constructors for the classes derived from `Polyhedron' follow
the pattern familiar from the classes already described.  The
constructors for the classes described below have identical arguments:
First, a `Point' specifying the center, then a `real' for the diameter
of the surrounding circle (_Umkreis_, in German) of one of its
polygonal faces, followed by three `real' arguments for the angles of
rotation about the main axes.

* Menu:

* Tetrahedron Getstart::
* Dodecahedron Getstart::
* Icosahedron Getstart::


File: 3DLDF.info,  Node: Tetrahedron Getstart,  Next: Dodecahedron Getstart,  Prev: Polyhedron Getstart,  Up: Polyhedron Getstart

Tetrahedron
-----------

   The center of a tetrahedron is the intersection of the lines from a
vertex to the center of the opposite side.  At least, in 3DLDF, this is
the `center' of a `Tetrahedron'.  I'm not 100 degrees  certain that
this is mathematically correct.

     Tetrahedron t(origin, 4);
     t.draw();
     t.get_center().dotlabel("$c$");

   [Figure 39 not displayed.]


File: 3DLDF.info,  Node: Dodecahedron Getstart,  Next: Icosahedron Getstart,  Prev: Tetrahedron Getstart,  Up: Polyhedron Getstart

Dodecahedron
------------

   A dodecahedron has 12 similar regular pentagonal faces.  The
following examples show the same `Dodecahedron' using different
projections:

     default_focus.set(2, 5, -10, 2, 5, 10, 10);
     Dodecahedron d(origin, 3);
     d.draw();

   [Figure 40 not displayed.]

   [Figure 41 not displayed.]

   Please note that the `Dodecahedron' in [next figure]  is drawn, and
not filldrawn!

   [Figure 42 not displayed.]

   [Figure 43 not displayed.]

   In [next figure] , `d' is filldrawn.  In this case, the surface
hiding algorithm has worked properly.  *Note Surface Hiding::.

   [Figure 44 not displayed.]


File: 3DLDF.info,  Node: Icosahedron Getstart,  Prev: Dodecahedron Getstart,  Up: Polyhedron Getstart

Icosahedron
-----------

   An icosahedron has 20 similar regular triangular faces.  The
following examples show the same `Icosahedron' using different
projections:

     default_focus.set(3, 0, -10, 2, 0, 10, 10);
     Icosahedron i(origin, 3);
     i.draw();

   [Figure 45 not displayed.]

   [Figure 46 not displayed.]

   [Figure 47 not displayed.]

   [Figure 48 not displayed.]

   In [next figure] , `i' is filldrawn.  In this case, the surface
hiding algorithm has worked properly.  *Note Surface Hiding::.

   [Figure 49 not displayed.]


File: 3DLDF.info,  Node: Pictures,  Next: Intersections,  Prev: Solid Figures,  Up: Top

Pictures
********

   Applying drawing and filling operations to the drawable objects
described in the previous chapters isn't enough to produce output.
These operations merely modify the `Picture' object that was passed to
them as an argument (`current_picture', by default).

   `Pictures' in 3DLDF are quite different from `pictures' in MetaPost.
When a drawing or filling operation is applied to an object O, a copy
of O, C, is allocated on the free store, a pointer to `Shape' S is
pointed at C, and S is pushed onto the `vector<Shape*> shapes' on the
`Picture' P, which was passed as an argument to the drawing or filling
command.  The arguments for the pen, dash pattern, `Color', and any
others, are used to set the corresponding data members of C (not O).

   In order to actually cause MetaPost code to be written to the output
file, it is necessary to invoke P.`output()'.  Now, the appropriate
version of `output()' is applied to each of the objects pointed to by a
pointer on P.`shapes'.  `output()' is a pure virtual function in
`Shape', so all classes derived from `Shape' must have an `output()'
function.  So, if `shapes[0]' points to a `Path', `Path::output()' is
called, if `shapes[1]' points to a `Point', `Point::output()' is
called, and if `shapes[2]' points to an object of a type derived from
`Solid', `Solid::output()' is called.  `Point', `Path', and `Solid' are
namely the only classes derived from `Shape' for which a version of
`output()' is defined.  All other `Shapes' are derived from one of
these classes.  These `output()' functions then write the MetaPost code
to the output file through the output file stream `out_stream'.

     beginfig(1);
     default_focus.set(0, 0, -10, 0, 0, 10, 10);
     Circle c(origin, 3, 90);
     c.draw();
     c.shift(1.5);
     c.draw();
     current_picture.output();
     endfig(1);

   [Figure 50 not displayed.]

   The C++  code for [the previous figure]  starts with the command
`beginfig(1)' and ends with the command `endfig(1)'.  They simply write
"`beginfig(<arg> )'" and "`endfig()'" to `out_stream', The optional
`unsigned int' argument to `endfig()' is not written to `out_stream',
it's merely "syntactic sugar" for the user.

   In MetaPost, the `endfig' command causes output and then clears
`currentpicture'.  This is not the case in 3DLDF, where
`Picture::output()' and `Picture::clear()' must be invoked explicitly:

     beginfig(1);
     Point p0;
     Point p1(1, 2, 3);
     p0.draw(p1);
     current_picture.output();
     endfig(1);
     
     beginfig(2);
     current_picture.clear();
     Circle C(origin, 3);
     C.fill();
     current_picture.output();
     endfig(2);

   In [next figure] , two `Pictures' are used within a single figure.

     beginfig(1);
     Picture my_picture;
     default_focus.set(0, 0, -10, 0, 0, 10, 10);
     Circle c(origin, 3, 90);
     c.draw(my_picture);
     my_picture.output();
     c.shift(1.5);
     c.fill(light_gray);
     current_picture.output();
     endfig(1);

   [Figure 51 not displayed.]

   Multiple objects, or complex objects made up of sub-objects, can be
stored in a `Picture', so that operations can be applied to them as a
group:

     default_focus.set(7, 5, -10, 7, 5, 10, 10);
     Cuboid c0(origin, 5, 5, 5);
     c0.shift(0, 0, 3);
     c0.draw();
     Circle z0(c0.get_rectangle_center(0), 2.5, 90, 0, 0, 64);
     z0.draw();
     Circle z1(z0);
     z1.shift(0, 0, -1);
     z1.draw();
     int i;
     int j = z0.get_size();
     for (i = 0; i < 8; ++i)
         z0.get_point(i * j/8).draw(z1.get_point(i * j/8));
     Cuboid c1(c0.get_rectangle_center(4), 5, 3, 3);
     c1.shift(0, 2.5);
     c1.draw();
     Rectangle r0 = *c1.get_rectangle_ptr(3);
     Point p[10];
     for (i = 0; i < 4; ++i)
       p[i] = r0.get_point(i);
     p[4] = r0.get_mid_point(0);
     p[5] = r0.get_mid_point(2);
     p[6] = p[4].mediate(p[5], 2/3.0);
     Circle z2(p[6], 2, 90, 90, 0, 16);
     z2.draw();
     Circle z3 = z2;
     z3.shift(3);
     z3.draw();
     j = z2.get_size();
     for (i = 0; i < 8; ++i)
         z2.get_point(i * j/8).draw(z3.get_point(i * j/8));
     p[7] = c0.get_rectangle_center(2);
     p[7].shift(-4);
     p[8] = c0.get_rectangle_center(3);
     p[8].shift(4);
     current_picture.output();
     current_picture.rotate(45, 45);
     current_picture.shift(10, 0, 3);
     current_picture.output();

   [Figure 52 not displayed.]

   Let's say the complex object in [the previous figure]  represents a
furnace.  From the point of view of 3DLDF, however, it's not an object
at all, and the drawing consists of a collection of unrelated
`Cuboids', `Circles', `Rectangles', and `Paths'.  If we hadn't put it
into a `Picture', we could still have rotated and shifted it, but only
by applying the operations to each of the sub-objects individually.

   One consequence of the way `Pictures' are output in 3DLDF is, that
the following code will not work:

     beginfig(1);
     Point p(1, 2);
     Point q(1, 3);
     out_stream << "pickup pencircle scaled .5mm;" << endl;
     origin.draw(p);
     out_stream << "pickup pensquare xscaled .3mm rotated 30;" << endl;
     origin.draw(q);
     current_picture.output();
     endfig();

   This is the MetaPost code that results:

     beginfig(1);
     pickup pencircle scaled .5mm;
     pickup pensquare xscaled .3mm rotated 30;
     draw (0.000000cm, -3.000000cm) -- (1.000000cm, -1.000000cm);
     draw (0.000000cm, -3.000000cm) -- (1.000000cm, 0.000000cm);
     endfig;

   It's perfectly legitimate to write raw MetaPost code to
`out_stream', as in lines 4 and 6 of this example.  However, the
`draw()' commands do not cause any output to `out_stream'.  The
MetaPost drawing commands are written to `out_stream' when
`current_picture.output()' is called.  Therefore, the `pickup' commands
are "bunched up" before the drawing commands.  In this example, setting
`currentpen' to `pencircle scaled .5mm' has no effect, because it is
immediately reset to `pensquare xscaled .3mm rotated 30' in the
MetaPost code, before the `draw' commands.  It is not possible to
change `currentpen' in this way within a `Picture'.  Since the `draw()'
commands in the 3DLDF code didn't specify a PEN argument, `currentpen'
with its final value is used for both of the MetaPost `draw' commands.
For any given invocation of `Picture::output()', there can only be one
value of `currentpen'.  All other pens must be passed as arguments to
the drawing commands.

* Menu:

* Projections::
* Focuses Getstart::
* Surface Hiding::


File: 3DLDF.info,  Node: Projections,  Next: Focuses Getstart,  Prev: Pictures,  Up: Pictures

Projections
===========

   In order for a 3D graphic program to be useful, it must be able to
make two-dimensional projections of its three-dimensional constructions
so that they can be displayed on computer screens and printed out.
These are some of the possible projections:

* Parallel projection onto one of the major planes
     These projections are trivial, and can be performed by 3DLDF.
     They are discussed in the following section.

* Parallel projection onto another plane
     I haven't programmed these projections yet, but they might be
     useful, so I probably will, when I get around to it.

* The perspective projection
     This is the projection most people think of, when they think of
     3D-graphics.  It is discussed in detail in *Note The Perspective
     Projection::.

* The isometric and axonometric projections
     These projections are important for engineering and drafting.  I
     have not yet implemented them in 3DLDF, but they are on my list of
     "Things To Do".

   The function `Picture::output()' takes a `const unsigned short'
argument specifying the projection to be used.  The user should
probably avoid using explicit `unsigned shorts', but should use the
constants defined for this purpose in the "namespace" `Projections'.(1)
The constants are `PERSP', `PARALLEL_X_Y', `PARALLEL_X_Z',
`PARALLEL_Z_Y', `AXON', and `ISO'.  The latter two should not be used,
because the axonometric and isometric projections have not yet been
implemented.

* Menu:

* Parallel Projections::
* The Perspective Projection::

   ---------- Footnotes ----------

   (1) Namespaces are described in Stroustrup, `The C++  Programming
Language', Chapter 8.

