Copyright © 2003, 2004, 2005, 2006, 2007 The Free Software Foundation
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".
Please note that the figures cannot be shown in the Info output format.
3DLDF is a free software package for three-dimensional drawing written by Laurence D. Finston, who is also the author of this manual. It is written in C++ using CWEB and it outputs MetaPost code.
3DLDF is a GNU package. It is part of the GNU Project of the Free Software Foundation and is published under the GNU General Public License. See the website http://www.gnu.org for more information. 3DLDF is available for downloading from http://ftp.gnu.org/gnu/3dldf. The official 3DLDF website is http://www.gnu.org/software/3dldf. More information about 3DLDF can be found at the author's website: http://wwwuser.gwdg.de/~lfinsto1.
Please send bug reports to:
bug-3DLDF@gnu.org and
Two other mailing lists may be of interest to users of 3DLDF: help-3DLDF@gnu.org is for people to ask other users for help and info-3DLDF@gnu.org is for sending announcements to users. To subscribe, send an email to the appropriate mailing list or lists with the word "subscribe" as the subject. The author's website is http://wwwuser.gwdg.de/~lfinsto1.
My primary purpose in writing 3DLDF was to make it possible to use MetaPost for three-dimensional drawing. I've always enjoyed using MetaPost, and thought it was a shame that I could only use it for making two-dimensional drawings. 3DLDF is a front-end that operates on three-dimensional data, performs the necessary calculations for the projection onto two dimensions, and writes its output in the form of MetaPost code.
While 3DLDF's data types and operations are modelled on those of Metafont and MetaPost, and while the only form of output 3DLDF currently produces is MetaPost code, it is nonetheless not in principle tied to MetaPost. It could be modified to produce PostScript code directly, or output in other formats. It would also be possible to modify 3DLDF so that it could be used for creating graphics interactively on a terminal, by means of an appropriate interface to the computer's graphics hardware.
The name "3DLDF" ("3D" plus the author's initials) was chosen because, while not pretty, it's unlikely to conflict with any of the other programs called "3D"-something.
This handbook, and the use of 3DLDF itself, presuppose at least some
familiarity on the part of the reader with Metafont, MetaPost,
CWEB, and C++
. If you are not familiar with any or all of them, I
recommend the following sources of information:
Knuth, Donald Ervin.
The METAFONTbook.
Computers and Typesetting; C.
Addison Wesley Publishing Company, Inc.
Reading, Massachusetts 1986.
Hobby, John D.
A User's Manual for MetaPost.
AT & T Bell Laboratories.
Murray Hill, NJ. No date.
Knuth, Donald E. and Silvio Levy.
The CWEB System of Structured Documentation.
Version 3.64--February 2002.
Stroustrup, Bjarne.
The C++
Programming Language.
Special Edition.
Reading, Massachusetts 2000.
Addison-Wesley.
ISBN 0-201-70073-5.
The manuals for MetaPost and CWEB are available from the Comprehensive TeX Archive Network (CTAN). See one of the following web sites for more information:
This manual has been created using Texinfo, a documentation system which is part of the GNU Project, whose main sponsor is the Free Software Foundation. Texinfo can be used to generate online and printed documentation from the same input files.
For more information about Texinfo, see:
Stallmann, Richard M. and Robert J. Chassell.
Texinfo. The GNU Documentation Format.
The Free Software Foundation. Boston 1999.
For more information about the GNU Project and the Free Software Foundation, see the following web site: http://www.gnu.org.
The edition of this manual is 1.1.5.1 and it documents version 1.1.5.1 of 3DLDF. The edition number of the manual and the version number of the program are the same (as of 18 July 2007), but may diverge at a later date.
Note that "I", "me", etc., in this manual refers to Laurence D. Finston, so far the sole author of both 3DLDF and this manual. "Currently" and similar formulations refer to version 1.1.5.1 of 3DLDF as of 18 July 2007.
This manual is intended for both beginning and advanced users of 3DLDF. So, if there's something you don't understand, it's probably best to skip it and come back to it later. Some of the more difficult points, or ones that presuppose familiarity with features not yet described, are in the footnotes.
I firmly believe that an adequate program with good documentation is more useful than a great program with poor or no documentation. The ideal case, of course, is a great program with great documentation. I'm sorry to say, that this manual is not yet as good as I'd like it to be. I apologize for the number of typos and other errors. I hope they don't detract too much from its usefulness. I would have liked to have proofread and corrected it again before publication, but for reasons external to 3DLDF, it is necessary for me to publish now. I plan to set up an errata list on the official 3DLDF website, and/or my own website.
Unless I've left anything out by mistake, this manual documents all of the data types, constants and variables, namespaces, and functions defined in 3DLDF. However, some of the descriptions are terser than I would like, and I'd like to have more examples and illustrations. There is also more to be said on a number of topics touched on in this manual, and some topics I haven't touched on at all. In general, while I've tried to give complete information on the "what and how", the "why and wherefore" has sometimes gotten short shrift. I hope to correct these defects in future editions.
Data types are formatted like this: int,
Point, Path. Plurals are formatted in the same way:
ints, Points, Paths. It is poor
typographical practice to typeset a single word using more than one
font, e.g., ints, Points, Paths. This applies to
data types whose plurals do not end in "s" as well, e.g.,
the plural of the C++
class Polyhedron is Polyhedra.
When C++
functions are discussed in this manual, I always include a
pair
of parentheses to make it clear that the item in question is a function
and not a variable, but I generally do not
include the arguments. For example, if I mention the
function
foo(), this doesn't imply that foo() takes no
arguments. If it were appropriate, I would include the argument type:
foo(int)
or the argument type and a placeholder name:
foo(int arg)
or I would write
foo(void)
to indicate that foo() takes no arguments. Also, I
generally don't indicate the return type, unless it is relevant. If it
is a member function
of a class, I may indicate this,
e.g.,, bar_class::foo(), or not,
depending on whether this information is relevant. This convention
differs from that used in the Function Index, which is generated
automatically by Texinfo. There, only the name of the function appears,
without parentheses, parameters, or return values. The class type
of member functions may appear in the Function Index, (e.g.,
bar_class::foo), but only in index entries that have been entered
explicitly by the author; such entries are not generated by Texinfo
automatically.
Examples are formatted as follows:
Point p0(1, 2, 3);
Point p1(5, 6, 7.9);
Path pa(p0, p1);
p0.show("p0:");
-| p0: (1, 2, 3)
The beautiful mathematical typesetting produced by TeX unfortunately does not appear in the Info and HTML versions of this manual. In these, the following symbols are used to replace the proper mathematical symbols.
a^2 as
"a squared".
x_1 as
"x sub one".
x * y as
"x times y".
sqrt(x) as
"the square root of x".
In addition, examples can contain the following symbols:
This manual does not use all of the symbols provided by Texinfo. If you
find a symbol you don't understand in this manual (which shouldn't
happen), see page 103 of the Texinfo manual.
Symbols:
The illustrations in this manual have been created using 3DLDF. The
code that generates them is in the Texinfo files themselves, that
contain the text of the manual. Texinfo is based on TeX, so it's
possible to make use of the latter's facility for writing ASCII text to
files using TeX's \write command.
The file 3DLDF-1.1.5.1/CWEB/exampman.web contains the
C++
code, and the file 3DLDF-1.1.5.1/CWEB/examples.mp
contains the MetaPost code for generating the illustrations.
3DLDF was built using GCC 2.95 when the illustrations were generated.
For some reason, GCC 3.3 has difficulty with them. It works to generate
them in batches of about 50 with GCC 3.3.
MetaPost outputs Encapsulated PostScript files. These can be included in TeX files, as explained below. However, in order to display the illustrations in the HTML version of this manual, I had to convert them to PNG ("Portable Network Graphics") format. See Converting EPS Files, for instructions on how to do this.
Please note that the illustrations cannot be shown in the Info output format!
If you have problems including the illustrations in the printed version,
for example, if your
installation doesn't have dvips, look for the following lines
in 3DLDF.texi:
\doepsftrue %% One of these two lines should be commented-out.
%\doepsffalse
Now, remove the % from in front of \doepsffalse and put
one in front of \doepsftrue. This will prevent the illustrations
from being included. This should only be done as a last resort,
however, because it will make it difficult if
not impossible to understand this manual.
The C++ code in an example is not always the complete code used to create the illustration that follows it, since the latter may be cluttered with commands that would detract from the clarity of the example. The actual code used always follows the example in the Texinfo source file, so the latter may be referred to, if the reader wishes to see exactly what code was used to generate the illustration.
You may want to skip the following paragraphs in this section, if you're reading this manual for the first time. Don't worry if you don't understand it, it's meaning should become clear after reading the manual and some experience with using 3DLDF.
The file 3DLDF.texi in the directory
3DLDF-1.1.5.1/DOC/TEXINFO, the driver file for this manual, contains
the following TeX code:
\newif\ifmakeexamples
\makeexamplestrue %% One of these two lines should be commented-out.
%\makeexamplesfalse
When texi2dvi is run on 3DLDF.texi,
\makeexamplestrue is not commented-out, and
\makeexamplesfalse is,
the C++
code for the illustrations is written to the file
examples.web.
If the EPS files don't already exist (in the directory
3DLDF-1.1.5.1/DOC/TEXINFO/EPS),
the TeX macro \PEX,
which includes them in the Texinfo files, will signal an error each time
it can't find one. Just type s at the command line to tell
TeX to keep going.
If you want to be sure that these are indeed the only errors, you can
type <RETURN> after each one instead.
texi2dvi 3DLDF.texi also generates the file
extext.tex, which contains TeX code for including the
illustrations by themselves.
examples.web must now be moved to 3DLDF-1.1.5.1/CWEB/ and
ctangled, examples.c must compiled,
and 3DLDF must be relinked. ctangle examples also generates
the header file example.h, which is included
in main.web. Therefore, if the contents of examples.h have
changed since the last time main.web was ctangled,
main.web will have to be ctangled, and main.c recompiled,
before 3dldf is relinked.1
Running 3dldf and MetaPost now
generates the EPS (Encapsulated PostScript) files
3DLDFmp.1 through (currently) 3DLDFmp.199
for the illustrations. They must be moved to
3DLDF-1.1.5.1/DOC/TEXINFO/EPS.
Now, when texi2dvi 3DLDF.texi is run again, the
dvips command
\epsffile includes the EPS files for the illustrations in the
manual. 3DLDF.texi includes the line \input epsf, so
that \epsffile works.
Of course, dvips (or some other program that does the
job) must be used to convert 3DLDF.dvi to a PostScript file.
To see exactly how this is done, take a look at the
.texi source files of this manual.2
In the 3DLDF.texi belonging to the 3DLDF distribution,
\makeexamplestrue will be commented-out, and
makeexamplesfalse won't be, because the EPS files for the
illustrations are included in the distribution.
The version of examples.web in 3DLDF-1.1.5.1/CWEB merely
includes the files subex1.web and subex2.web.
If you rename 3DLDF-1.1.5.1/CWEB/exampman.web to examples.web,
you can generate the illustrations.
As mentioned above, 3DLDF has been programmed using CWEB, which is a "literate programming" tool developed by Donald E. Knuth and Silvio Levy. See Sources of Information, for a reference to the CWEB manual. Knuth's TeX--The Program and Metafont--The Program both include a section "How to read a WEB" (pp. x-xv, in both volumes).
CWEB files combine source code
and documentation. Running ctangle on a CWEB file,
for example, main.web, produces the file main.c containing
C or C++
code. Running cweave main.web creates a
TeX file with pretty-printed source code and nicely formatted
documentation. I find that using CWEB makes it more natural to
document my code as I write it, and makes the source files easier to
read when editing them. It does have certain consequences
with regard to compilation, but these are taken care of by make.
See Adding a File, and Changes, for more
information.
The CWEB files in the directory 3DLDF-1.1.5.1/CWEB/ contain the
source code for 3DLDF. The file 3DLDFprg.web in this directory
is only ever used for cweaving; it is never ctangled and contains no
C++
code for compilation. It does, however, include all of the other
CWEB files, so that cweave 3DLDFprg.web generates the TeX file
containing the complete documentation of the source code of 3DLDF.
The files 3DLDF-1.1.5.1/CWEB/3DLDFprg.tex,
3DLDF-1.1.5.1/CWEB/3DLDFprg.dvi, and
3DLDF-1.1.5.1/CWEB/3DLDFprg.ps are
included in the distribution of 3DLDF as a
convenience. However, users may generate them themselves, should there
be some reason for doing so, by entering make ps
from the command line of a shell from the working
directory 3DLDF-1.1.5.1/ or 3DLDF-1.1.5.1/CWEB.
Alternatively, the user may generate them
by hand from the working directory 3DLDF-1.1.5.1/CWEB/ in the
following way:
cweave 3DLDFprg.web
generates 3DLDFprg.tex.
tex 3DLDFprg or tex 3DLDFprg.tex generates
3DLDFprg.dvi.
dvips -o 3DLDFprg.ps 3DLDFprg (possibly with additional options)
generates 3DLDFprg.ps.
lpr -P<print queue> 3DLDFprg.ps
sends 3DLDFprg.ps to a printer, on a UNIX or UNIX-like system.
The individual commands may differ, depending on the system you're using.
Metafont is a system created by Donald E. Knuth for generating fonts, in particular for use with TeX, his well-known typsetting system.3 Expressed in a somewhat simplified way, Metafont is a system for programming curves, which are then digitized and output in the form of run-time encoded bitmaps. (See Knuth's The Metafontbook for more information).
John D. Hobby modified Metafont's source code to create
MetaPost, which functions in much the same way, but outputs
encapsulated PostScript (EPS) files instead of bitmaps. MetaPost is
very useful for creating graphics and is a convenient
interface to PostScript. It is also easy both to imbed
TeX code in MetaPost programs, for instance, for typesetting
labels, and to include MetaPost graphics in ordinary TeX
files, e.g., by using dvips.4
Apart from simply printing the PostScript file output by
dvips, there are many programs that can process ordinary
or encapsulated PostScript files and convert them to other formats.
Just two of the many possibilities are ImageMagick and GIMP, both of
which can be used to create animations from MetaPost graphics.
However, MetaPost inherited a significant limitation from Metafont: it's not possible to use it for making three-dimensional graphics, except in a very limited way. One insuperable problem is the severe limitation on the magnitude of user-defined numerical variables in Metafont and MetaPost.5 This made sense for Metafont's and MetaPost's original purposes, but they make it impossible to perform the calculations needed for 3D graphics.
Another problem is the data types defined in Metafont: Points are represented as pairs of real values and affine transformations as sets of 6 real values. This corresponds to the representation of points and affine transformations in the plane as a two-element vector on the one hand and a six element matrix on the other. While it is possible to work around the limitation imposed by having points be represented by only two values, it is impracticable in the case of the transformations.
For these reasons, I decided to write a program that would behave more or less like Metafont, but with suitable extensions, and the ability to handle three dimensional data; namely 3DLDF. It stores the data and performs the transformations and other necessary calculations and is not subject to the limitations of MetaPost and its data types. Upon output, it performs a perspective transformation, converting the 3D image into a 2D one. The latter can now be expressed as an ordinary MetaPost program, so 3DLDF writes its output as MetaPost code to a file.
In the following, it may be a little unclear why I sometimes refer to
Metafont and sometimes to MetaPost. The reason is that Metafont
inherited much of its functionality from Metafont. Certain operations
in Metafont have no meaning in MetaPost and so have been removed, while
MetaPost's function of interfacing with PostScript has caused other
operations to be added. For example, in MetaPost, color is a
data type, but not in Metafont. Unless otherwise stated, when I refer to
Metafont, it can be assumed that what I say applies to MetaPost as well.
However, when I refer to MetaPost, it will generally be in connection
with features specific to MetaPost.
When 3DLDF is run, it uses the three-dimensional data contained in the
user code to create a two-dimensional projection.
Currently, this can be a perspective projection, or a parallel
projection onto one of the major planes. MetaPost code representing
this projection is then written to the output file.
3DLDF does no scan conversion,6
so all of the curves in the projection are
generated by means of the algorithms MetaPost inherited from Metafont.
These algorithms, however, are designed to find the
"most pleasing curve"7
given one or more two-dimensional points and connectors; they do not
account for the the fact that the two-dimensional points are projections
of three-dimensional ones. This can lead to unsatisfactory results,
especially where extreme foreshortening occurs. In particular,
curl, dir, tension, and control points should be
used cautiously, or avoided altogether, when specifying connectors.
3DLDF operates on the assumption that, given an adequate number of points, MetaPost will produce an adequate approximation to the desired curve in perspective, since the greater the number of points given for a curve, the less "choice" MetaPost has for the path through them. My experience with 3DLDF bears this out. Generally, the curves look quite good. Where problems arise, it usually helps to increase the number of points in a curve.
A more serious problem is the imprecision resulting from the operation of rotation. Rotations use the trigonometric functions, which return approximate values. This has the result that points that should have identical coordinate values, sometimes do not. This has consequences for the functions that compare points. The more rotations are applied to points, the greater the divergence between their actual coordinate values, and the values they should have. So far, I haven't found a solution for this problem. On the other hand, it hasn't yet affected the usability of 3DLDF.
3DLDF does not yet include a routine for reading input files. This means that user code must be written in C++ , compiled, and linked with the rest of the program. I admit, this is not ideal, and writing an input routine for user code is one of the next things I plan to add to 3DLDF.
I plan to use Flex and Bison to write the input routine.8 The syntax of the input code should be as close as possible to that of MetaPost, while taking account of the differences between MetaPost and 3DLDF.
For the present, however, the use of 3DLDF is limited to
those who feel comfortable using C++
and compiling and
relinking programs. Please don't be put off by this! It's not so
difficult, and make does most of the work of recompiling and
running 3DLDF. See Installing and Running 3DLDF, for more
information.
I originally developed 3DLDF on a DECalpha Personal Workstation with two processors running under the operating system Tru64 Unix 5.1, using the DEC C++ compiler. I then ported it to a PC Pentium 4 running under Linux 2.4, using the GNU C++ compiler GCC 2.95.3, and a PC Pentium II XEON under Linux 2.4, using GCC 3.3. I am currently only maintaining the last version. I do not believe that it's worthwhile to maintain a version for GCC 2.95. While I would like 3DLDF to run on as many platforms as possible, I would rather spend my time developing it than porting it. This is something where I would be grateful for help from other programmers.
Although I am no longer supporting ports to other systems, I have left some conditionally compiled code for managing platform dependencies in the CWEB sources of 3DLDF. This may make it easier for other people who want to port 3DLDF to other platforms.
Currently, the files io.web, loader.web, main.web,
points.web,
and pspglb.web contain conditionally compiled code, depending on
which compiler, or in the case of GCC, which version of the compiler, is
used. The DEC C++
compiler defines the preprocessor macro
__DECCXX and GCC defines __GNUC__. In order to
distinguish between GCC 2.95.3 and GCC 3.3, I've added the macros
LDF_GCC_2_95 and LDF_GCC_3_3 in loader.web, which
should be defined or undefined, depending on which compiler you're
using. In the distribution, LDF_GCC_3_3 is defined and
LDF_GCC_2_95 is undefined, so if you want to try using GCC 2.95, you'll
have to change this (it's not guaranteed to work).
3DLDF 1.1.5.1 now uses Autoconf and Automake, and the
configure script generates a config.h file, which is now
included in loader.web. Some of
the preprocessor macros defined in config.h are used to
conditionally include library header files, but so far, there is no error
handling code for the case that a file can't be included. I hope to improve the
way 3DLDF works together with Autoconf and Automake in the near future.
3DLDF 1.1.5 is the first release that contains template functions. Template instantiation differs from compiler to compiler, so using template functions will tend to make 3DLDF less portable. See Template Functions, for more information. I am no longer able to build 3DLDF on the DECalpha Personal Workstation. I'm fairly sure that it would be possible to port it, but I don't plan to do this, since Tru64 Unix 5.1 and the DEC C++
compiler are non-free software.
So far, I've been the sole author and user of 3DLDF. I would be very interested in having other programmers contribute to it. I would be particularly interested in help in making 3DLDF conform as closely as possible to the GNU Coding Standards. I would be grateful if someone would write proper Automake and Autoconf files, since I haven't yet learned how to do so (I'm working on it).
See Introduction, for information on how to contact the author.
Since 3DLDF does not yet have an input routine, user code must be
written in C++
(in main.web, or some other file) and compiled.
Then, 3DLDF must be relinked, together with the new file of object
code resulting from the compilation.
For now, the important point is that the text of
the examples in this manual represent C++
code.
See Installing and Running 3DLDF, for more information.
The most basic drawable object in 3DLDF is class Point. It is
analogous to pair in Metafont. For example, in Metafont one
can define a pair using the "z" syntax as
follows:
z0 = (1cm, 1cm);
There are other ways of defining pairs in Metafont (and
MetaPost), but this is the usual way.
In 3DLDF, a Point is declared and initialized as follows:
Point pt0(1, 2, 3);
This simple example demonstrates several differences between Metafont
and 3DLDF. First of all, there is no analog in 3DLDF to Metafont's
"z" syntax.
If I want to have Points called "pt0", "pt1",
"pt2", etc., then I must declare each of them to be a
Point:
Point pt0(10, 15, 2);
Point pt1(13, 41, 5.5);
Point pt2(62.9, 7.02, 8);
Alternatively, I could declare an array of Points:
Point pt[3];
Now I can refer to pt[0], pt[1], and pt[2].
In the Metafont example, the x and y-coordinates of the pair z0
are specified using the unit of measurement, in this case, centimeters.
This is currently not possible in 3DLDF. The current unit of
measurement is stored in the static variable Point::measurement_units,
which is a string. Its default value is "cm" for
"centimeters".
At present, it is best to stick with one unit of measurement for a
drawing.
After I've defined an input routine, 3DLDF should handle
units of measurement in the same way that Metafont does.
Another difference is that the Points pt0, pt1, and
pt2 have three coordinates, x, y, and z, whereas z0 has
only two, x and y. Actually, the difference goes deeper than this. In
Metafont, a pair has two parts, xpart and ypart,
which can be examined by the user. In 3DLDF, a Point contains
the following sets of coordinates:
world_coordinates
user_coordinates
view_coordinates
projective_coordinates
These are sets of 3-dimensional homogeneous coordinates, which means that they contain four coordinates: x, y, z, and w. Homogeneous coordinates are used in the affine and perspective transformations (see Transforms).
Currently, only world_coordinates and
projective_coordinates are used in 3DLDF.
The world_coordinates refer to the position of a Point in
3DLDF's basic, unchanging coordinate system.
The projective_coordinates are the coordinates of the
two-dimensional projection of the Point onto a plane.
This projection is what is ultimately printed out or displayed on the
computer screen. Please note, that when the coordinates of a
Point are referred to in this manual, the
world_coordinates are meant, unless otherwise stated.
Points can be declared and their values can be set in different
ways.
Point pt0;
Point pt1(1);
Point pt2(2.3, 52);
Point pt3(4.5, 7, 13.205);
pt0 is declared without any arguments, i.e., using the default
constructor, so the values of its x, y, and
z-coordinates are all 0.
pt1 is declared and initialized with one argument for the x-coordinate,
so its y and z-coordinates are initialized with the values of
CURR_Y and CURR_Z respectively.
The latter are static constant data members
of class Point, whose values are 0 by default. They can be reset
by the user, who should
make sure that they have sensible values.
pt2 is declared and initialized with two arguments for its x and
y-coordinates, so its z-coordinate is initialized to the value of
CURR_Z. Finally, pt3 has an argument for each of its
coordinates.
Please note that pt0 is constructed using a the default
constructor, whereas the other Points are constructed using a
constructor with one required argument (for the x-coordinate), and two
optional arguments (for the y and z-coordinates). The default
constructor always sets all the coordinates to 0, irrespective of the
values of CURR_Y and CURR_Z.
It is possible to change the value of the coordinates of Points
by using the assignment operator =
(Point::operator=()) or the function Point::set()
(with appropriate arguments):
Point pt0(2, 3.3, 7);
Point pt1;
pt1 = pt0;
pt0.set(34, 99, 107.5);
pt0.show("pt0:");
-| pt0: (34, 99, 107.5)
pt1.show("pt1:");
-| pt1: (2, 3.3, 7)
In this example, pt0 is initialized with the coordinates (2, 3.3, 7),
and pt1 with the coordinates (0, 0, 0).
pt1 = pt0 causes pt1 to have the same coordinates as
pt0, then the coordinates of pt0 are changed to (34,
99, 107.5). This doesn't affect pt1, whose coordinates remain
(2, 3.3, 7).
Another way of declaring and initializing Points is by using the
copy constructor:
Point pt0(1, 3.5, 19);
Point pt1(pt0);
Point pt2 = pt0;
Point pt3;
pt3 = pt0;
In this example, pt1 and pt2 are both declared and
initialized using the copy constructor; Point pt2 = pt0 does not
invoke the assignment operator. pt3, on the other hand, is
declared using the default constructor, and not initialized. In the
following line, pt3 = pt0 does invoke the assignment operator,
thus resetting the coordinate values of pt3 to those of
pt0.
Points don't always have to remain in the same place. There are
various ways of moving or transforming them:
shift, so I call it "shifting".
class Point has several member functions
for applying these affine transformations9
to a Point.
Most of the arguments to these functions are of
type real. As you may know, there is no such data type in C++
.
I have defined real using typedef to be either
float or double, depending on the value of a preprocessor
switch for conditional compilation.10
3DLDF uses many real values and I wanted to be able to
change the precision used by making one change (in the file
pspglb.web) rather than having to examine all the places in the
program where float or double are used. Unfortunately,
setting real to double currently doesn't work.
The function
shift()
adds its arguments to the corresponding
world_coordinates of a Point. In the following example,
the function show() is used to print the world_coordinates
of p0 to standard output.
Point p0(0, 0, 0);
p0.shift(1, 2, 3);
p0.show("p0:");
-| p0: (1, 2, 3)
p0.shift(10);
p0.show("p0:");
-| p0: (11, 2, 3)
p0.shift(0, 20);
p0.show("p0:");
-| p0: (11, 22, 3)
p0.shift(0, 0, 30);
p0.show("p0:");
-| p0: (11, 22, 33)
shift takes three real arguments, whereby the second and
third are optional. To shift a Point in the direction of
the positive or negative y-axis, and/or the positive or negative z-axis
only, then a 0 argument for the
x direction, and possibly one for the y direction
must be used as placeholders, as in the example above.
shift() can be invoked with a Point argument
instead of real arguments. In this case, the x, y, and
z-coordinates of the argument are used for shifting the Point:
Point a(10, 10, 10);
Point b(1, 2, 3);
a.shift(b);
a.show("a:")
-| a: (11, 12, 13)
Another way of shifting Points is to use the binary +=
operator (Point::operator+=()) with a Point
argument.
Point a0(1, 1, 1);
Point a1(2, 2, 2);
a0 += a1;
a0.show("a0:");
-| a0: (3, 3, 3)
The function scale() takes three real arguments.
The x, y, and z-coordinates of the Point are
multiplied by the first, second, and third arguments respectively. Only
the first argument is required; the default for the others is 1.
If one wants to perform scaling in either the y-dimension only, or the y and z-dimensions only, a dummy argument of 1 must be passed for scaling in the x-dimension. Similarly, if one wants to perform scaling in the z-dimension only, dummy arguments of 1 must be passed for scaling in the x and y-dimensions.
Point p0(1, 2, 3);
p0.scale(2, 3, 4);
p0.show("p0:");
-| p0: (2, 6, 12)
p0.scale(2);
p0.show("p0:");
-| p0: (4, 6, 12)
p0.scale(1, 3);
p0.show("p0:");
-| p0: (4, 18, 12)
p0.scale(1, 1, 3);
p0.show("p0:");
-| p0: (4, 18, 36)
Shearing is more complicated than shifting or scaling. The function
shear() takes six real arguments.
If p is a Point, then p.shear(a, b, c, d, e, f) sets
x_p to x_p + ay_p + bz_p, y_p to
y_p + cx_p + dz_p, and
z_p to z_p + ex_p + fy_p.
In this way, each coordinate of a Point is modified based on the
values of the other two coordinates, whereby the influence of the
other coordinates on the new value is weighted according to the
arguments.
Point p(1, 1, 1);
p.shear(1);
p.show("p:");
-| p: (2, 1, 1)
p.set(1, 1, 1);
p.shear(1, 1);
p.show("p:");
-| p: (3, 1, 1)
p.set(1, 1, 1);
p.shear(1, 1, 2, 2, 3, 3);
p.show("p:");
-| p: (3, 5, 7)
[next figure] demonstrates the effect of shearing the points of a rectangle in the x-y plane.
Point P0;
Point P1(3);
Point P2(3, 3);
Point P3(0, 3);
Rectangle r(p0, p1, p2, p3);
r.draw();
Rectangle q(r);
q.shear(1.5);
q.draw(black, "evenly");
Fig. 1.
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)
Fig. 2.
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.
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
array11
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.12
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
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.
See Point Reference; 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.
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)
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 (see Color Reference) and a
pen13.
Point P(0, 1);
P.drawdot(Colors::black, "pencircle scaled 3mm");
Fig. 3.
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().14
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");
Fig. 4.
For complete descriptions of drawdot() and undrawdot(),
see Point Reference; Drawing.
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);
Fig. 5.
p0.draw(p1) is equivalent in its effect to
p1.draw(p0).
The function Point::draw() takes a required Point&
argument (a reference15
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");
Fig. 6.
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");
Fig. 7.
For complete descriptions of draw() and undraw(),
see Point Reference; Drawing.
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");
Fig. 8.
For complete descriptions of Point::label() and
Point::dotlabel(), see Points; Labelling.
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 (see Focuses), 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.16
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();
Fig. 9.
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.
See 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.17
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();
Fig. 10.
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();
Fig. 11.
As mentioned above (see 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.18
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.
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();
Fig. 12.
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.
See Color Reference.
pa.fill(Colors::gray);
Fig. 13.
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.
See 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();
Fig. 14.
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");
Fig. 15.
Paths can also be "undrawn", "unfilled", and "unfilldrawn",
using the corresponding functions:
pa.fill(gray);
p0.undraw(p1, "", "pencircle scaled 3mm");
Fig. 16.
pa.fill(gray);
Path q;
q = pa;
q.scale(.5, .5);
q.unfill();
Fig. 17.
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");
Fig. 18.
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. See Surface Hiding, for more information.
p0.draw(p1);
pa.fill(gray);
q.unfilldraw(black, "", "pensquare xscaled 3mm yscaled 1mm");
Fig. 19.
See Paths; Drawing and Filling, for more information, and complete descriptions of the functions.
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 Polygon Reference, Rectangle Reference,
Ellipse Reference, and Circle Reference, for complete
information on these classes.
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();
Fig. 20.
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();
Fig. 21.
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();
Fig. 22.
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();
Fig. 23.
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();
Fig. 24.
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();
Fig. 25.
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);
}
Fig. 26.
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();
Fig. 27.
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();
Fig. 28.
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();
Fig. 29.
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();
}
Fig. 30.
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();
Fig. 31.
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();
Fig. 32.
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.
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();
Fig. 33.
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);
}
Fig. 34.
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();
Fig. 35.
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();
}
Fig. 36.
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.
See Accuracy.
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();
Fig. 37.
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();
Fig. 38.
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 (see 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.
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$");
Fig. 39.
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();
Fig. 40.
Fig. 41.
Please note that the Dodecahedron in [next figure]
is drawn, and not
filldrawn!
Fig. 42.
Fig. 43.
In [next figure]
, d is filldrawn. In this case,
the surface hiding algorithm has worked properly.
See Surface Hiding.
Fig. 44.
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();
Fig. 45.
Fig. 46.
Fig. 47.
Fig. 48.
In [next figure]
, i is filldrawn. In this case,
the surface hiding algorithm has worked properly.
See Surface Hiding.
Fig. 49.
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);
Fig. 50.
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);
Fig. 51.
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();
Fig. 52.
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.
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:
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.19
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.
When a Picture is projected onto the x-y plane, the
x and y-values from the world_coordinates of the Points
belonging to the objects on the
Picture are copied to
their projective_coordinates, which are
used in the MetaPost code written to out_stream.
If a Picture p contains an object in the x-y plane,
or in a plane parallel to the x-y plane, then
the result of p.output(Projections::PARALLEL_X_Y) is more-or-less
equivalent to just using MetaPost without 3DLDF.
Rectangle r(origin, 3, 3, 90);
Circle c(origin, 3, 90);
c *= r.shift(0, 0, 5);
r.draw();
c.draw();
current_picture.output(Projections::PARALLEL_X_Y);
Fig. 53.
If the objects do not lie in the x-y plane, or a plane parallel to the x-y plane, then the projection will be distorted:
current_picture.output(Projections::PARALLEL_X_Y);
Fig. 54.
Picture::output() can be called with an additional real
argument factor for magnifying or shrinking the Picture.
Rectangle r(origin, 4, 4, 90, 60);
Circle c(origin, 4, 90, 60);
c *= r.shift(0, 0, 5);
r.filldraw(black, gray);
c.unfilldraw(black);
current_picture.output(Projections::PARALLEL_X_Y, .5);
current_picture.shift(2.5);
current_picture.output(Projections::PARALLEL_X_Y);
current_picture.shift(1);
current_picture.output(Projections::PARALLEL_X_Y, 2);
Fig. 55.
Parallel projection onto the x-z and z-y planes are completely analogous to parallel projection onto the x-y plane.
The perspective projection obeys the laws of linear perspective. In 3DLDF, it is performed by means of a transformation, whose effect is, to the best of my knowledge, exactly equivalent to the result of a perspective projection done by hand using vanishing points and rulers.
It is very helpful to the artist to understand the laws of linear perspective, and to know how to make a perspective drawing by hand.20 However, it is a very tedious and error-prone procedure (I know, I've done it). One of my main motivations for writing 3DLDF was so I wouldn't have to do it anymore.
[next figure] shows a perspective construction, the way it could be done by hand. The point of view, or focus is located 6cm from the picture plane, and 4cm above the ground (or x-z) plane at the point (0, 4, -6). The rectangle R lies in the ground plane, with the point r_0 at (2, 0, 1.5). The right side of R, with length = 2cm lies at an angle of 40 to the ground line, which corresponds to the intersection line of the ground plane with the picture plane, and the left side, with length = 5cm, at an angle of 90 degrees - 40 degrees = 50 degrees to the ground line.
Fig. 56.
While it's possible to use 3DLDF to make a perspective construction in the traditional way, as [the previous figure] shows, the code for [next figure]
achieves the same result more efficiently:
default_focus.set(0, 4, -6, 0, 4, 6, 6);
Rectangle r(origin, 2, 5, 0, 40);
Point p(2, 0, 1.5);
r.shift(p - r.get_point(0));
r.draw();
Fig. 57.
In [the second-to-last figure]
, it was
convenient to start with the corner point r_0;
if we needed the center of R, it would have to be found from the
corner points.
However, in 3DLDF, Rectangles are most often constructed about
the center. Therefore, in [next figure]
, R is first
constructed about the origin, with the
rotation about the y-axis passed as an argument to the constructor.
It is then shifted such that *(R.points[0]), the first
(or zeroth, if you will) Point on R comes to lie at
(2, 0, 1.5).
Unlike the other transformations currently used in 3DLDF, the perspective transformation is non-affine. Affine transformations maintain parallelity of lines, while the rules of perspective state that parallel lines, with one exception, appear to recede toward a vanishing point.21
In [the second-to-last figure] , the lines from r_0 to r_1 and from r_3 to r_2 appear to vanish toward the right-hand 40 degrees vanishing point, while the lines from r_0 to r_3 and from r_1 to r_2 appear to vanish toward the left-hand 50 degrees vanishing point. The lower the angle of a vanishing point, the further away it is from the center of vision, as [next figure] shows:
Fig. 58.
In [the previous figure] , the 0.5 degrees vanishing point is nearly 5 and 3/4 meters away from the CV, and a line receding to it will be very nearly horizontal. However, the distance from the focus to the CV is only 5cm. As this distance increases, the distance from the CV to a given vanishing point increases proportionately. If the distance is 30cm, a more reasonable value for a drawing, then the x-coordinate of VP 10 degrees is 170.138cm, that of VP 5 degrees is 342.902cm, and that of VP 0.5 degrees is 3437.66cm! This is the reason why perspective drawings done by hand rarely contain lines receding to the horizon at low angles.
This problem doesn't arise when the perspective transformation is used. In this case, any angle can be calculated as easily as any other:
default_focus.set(0, 4, -6, 0, 4, 6, 6);
Rectangle r;
Point center(0, 2);
r.set(center, 2, 5, 0, 0, 0.5);
r.draw();
r.set(center, 2, 5, 0, 0, 2.5);
r.draw();
r.set(center, 2, 5, 0, 0, 5);
r.draw();
current_picture.output();
Fig. 59.
The perspective transformation requires a focus; as a consequence,
outputting a Picture requires an object of class
Focus.
Picture::output() takes an optional pointer-to-Focus
argument, which is 0 by default. If the default is used, (or 0 is
passed explicitly), the global variable default_focus is used.
See Focus Reference; Global Variables.
A Focus can be thought of as the observer of a scene, or a
camera. It contains a Point position for its location with
respect to 3DLDF's coordinate system, and a Point direction,
specifying the direction where the observer is looking, or where the
camera is pointed. The Focus can be rotated freely about the
line
PD,
where P stands for position and
D
for direction,
so a Focus contains a third Point up, to indicate which
direction will be "up" on the projection, when a Picture is
projected.
The projection plane q will always be perpendicular to the line PD, or to put it another way, the line PD, is normal to q.
Unlike the traditional perspective construction, where the distance from
the focus to the center of vision fixes both the location of the focus
in space, and its distance to the
picture plane,22
these two parameters can be set independently when the perspective
transformation is used.
The distance from a Focus to the picture plane is stored in the
data member distance, of type real.
A Focus can be declared using two Point arguments for
position and direction, and a real argument for
distance, in that order.
Point pos(0, 5, -10);
Point dir(0, 5, 10);
Focus f(pos, dir, 10);
Point center(2, 0, 3);
Rectangle r(center, 3, 3);
r.draw();
current_picture.output(f);
Fig. 60.
The "up" direction is calculated by the Focus constructor
automatically. An optional argument can be used to specify the angle by
which to rotate the Focus about
the line PD.
Point pos(0, 5, -10);
Point dir(0, 5, 10);
Focus f(pos, dir, 10, 30);
Point center(2, 0, 3);
Rectangle r(center, 3, 3);
r.draw();
current_picture.output(f);
Fig. 61.
Alternatively, a Focus can be declared using three real
arguments each for the x, y, and z-coordinates of position and
direction, respectively, followed by the real arguments
for distance and the angle of rotation:
Focus f(3, 5, -5, 0, 3, 0, 10, 10);
Point center(2, 0, 3);
Rectangle r(center, 3, 3);
r.draw();
current_picture.output(f);
Fig. 62.
Focuses contain two Transforms, transform and persp.
A Focus can be located anywhere in 3DLDF's coordinate system.
However, performing the perspective projection is more convenient, if
position and direction both lie on one of the major axes,
and the plane of projection corresponds to one of the major planes.
transform is the transformation which would have this affect on
the Focus, and is calculated by the Focus constructor.
When a Picture is output using that Focus,
transform is applied to all of the Shapes on the
Picture, maintaining the relationship between the Focus
and the Shapes, while making it easier to calculate the
projection. The Focus need never be
transformed by transform.
The actual perspective transformation is stored
in persp.
Focuses can be moved by using one of the setting functions, which
take the same arguments as the constructors.
Currently, there are no affine transformation functions for moving
Focuses, but I plan to add them soon. If 3DLDF is used for
making
animation, resetting the Focus can be used to simulate camera
movements:
beginfig(1);
Point pos(2, 10, 3);
Point dir(2, -10, 3);
Focus f;
Point center(2, 0, 3);
for (int i = 0; i < 5; ++i)
{
f.set(pos, dir, 10, (15 * i));
Rectangle r(center, 3, 3);
r.draw();
current_picture.output(f);
current_picture.clear();
pos.shift(1, 1, 0);
dir.rotate(0, 0, 10);
}
endfig(1);
Fig. 63.
In [the previous figure]
, current_picture is output 5 times within a single
MetaPost figure. Since the file passed to MetaPost is called
persp.mp, the file of Encapsulated PostScript (EPS) code
containing [the previous figure]
is called persp.1.
To use this technique for making an animation, it's necessary to output
the Picture into multiple MetaPost figures.
Point pos(2, 10, 3);
Point dir(2, -10, 3);
Focus f;
Point center(2, 0, 3);
for (int i = 0; i < 5; ++i)
{
f.set(pos, dir, 10, (15 * i));
Rectangle r(center, 3, 3);
r.draw();
beginfig(i+1);
current_picture.output(f);
endfig();
current_picture.clear();
pos.shift(1, 1, 0);
dir.rotate(0, 0, 10);
}
Now, running MetaPost on persp.mp generates the EPS files
persp.1, persp.2, persp.3, persp.4, and
persp.5, containing the five separate drawings of r.
In [next figure]
, Circle c lies in front of Rectangle
r.
Since c is drawn and not filled, r is visible behind
c.
default_focus.set(1, 3, -5, 0, 3, 5, 10);
Point p(0, -2, 5);
Rectangle r(p, 3, 4, 90);
r.draw();
Point q(2, -2, 3);
Circle c(q, 3, 90);
c.draw();
current_picture.output();
Fig. 64.
If instead, c is filled or filldrawn, only the parts of r that are not covered by c should be visible:
r.draw();
c.filldraw();
Fig. 65.
What parts of r are covered depend on the point of view, i.e.,
the position and direction of the Focus used for outputting the
Picture:
default_focus.set(8, 0, -5, 5, 3, 5, 10);
Fig. 66.
Determining what objects cover other objects in a program for 3D graphics is called surface hiding, and is performed by a hidden surface algorithm. 3DLDF currently has a very primitive hidden surface algorithm that only works for the most simple cases.
The hidden surface algorithm used in 3DLDF is a
painter's algorithm, which means that the objects that are
furthest away from the Focus are drawn first, followed by the
objects that are closer, which may thereby cover them. In order to make
this possible, the Shapes on a Picture must be sorted
before they are output. They are sorted according to the z-values in
the projective_coordinates of the Points belonging to the
Shape. This may seem strange, since the
projection is two-dimensional and only the x and y-values from
projective_coordinates are written to out_stream.
However, the perspective transformation also produces a z-coordinate,
which indicates the distance of the Points from the Focus
in the z-dimension.
The problem is, that all Shapes, except Points themselves,
consist of multiple Points, that may have different
z-coordinates. 3DLDF currently does not yet have a satisfactory way of
dealing with this situtation. In order to try to cope with it, the user
can specify four different ways of sorting the Shapes: They
can be sorted according to the maximum z-coordinate, the
minimum z-coordinate, the mean of the maximum and minimum z-coordinate
(max + min) / 2,
and not sorted.
In the last case, the Shapes are output in the order of the
drawing and filling commands in the user code.
The z-coordinates referred to are those in
projective_coordinates, and will have been calculated for a
particular Focus.
The function Picture::output() takes a
const unsigned short sort_value argument that specifies
which style of sorting
should be used. The namespace Sorting contains the following
constants which should be used for sort_value: MAX_Z,
MIN_Z, MEAN_Z, and NO_SORT. The default is
MAX_Z.
3DLDF's primitive hidden surface algorithm cannot work for objects that intersect. The following examples demonstrate why not:
using namespace Sorting;
using namespace Colors;
using namespace Projections;
default_focus.set(5, 3, -10, 3, 1, 1, 10, 180);
Rectangle r0(origin, 3, 4, 45);
Rectangle r1(origin, 2, 6, -45);
r0.draw();
r1.draw();
current_picture.output(default_focus, PERSP, 1, MAX_Z);
r0.show("r0:");
-| r0:
fill_draw_value == 0
(-1.5, -1.41421, -1.41421) -- (1.5, -1.41421, -1.41421) --
(1.5, 1.41421, 1.41421) -- (-1.5, 1.41421, 1.41421)
-- cycle;
r0.show("r0:", 'p');
-| r0:
fill_draw_value == 0
Perspective coordinates.
(-5.05646, -4.59333, -0.040577) -- (-2.10249, -4.86501, -0.102123) --
(-1.18226, -1.33752, 0.156559) -- (-3.51276, -1.2796, 0.193084)
-- cycle;
r1.show("r1:");
-| r1:
fill_draw_value == 0
(-1, 2.12132, -2.12132) -- (1, 2.12132, -2.12132) --
(1, -2.12132, 2.12132) -- (-1, -2.12132, 2.12132)
-- cycle;
r1.show("r1:", 'p');
-| r1:
fill_draw_value == 0
Perspective coordinates.
(-5.09222, -0.995681, -0.133156) -- (-2.98342, -1.03775, -0.181037) --
(-1.39791, -4.05125, 0.208945) -- (-2.87319, -3.93975, 0.230717)
-- cycle;
Fig. 67.
In [the previous figure]
, the Rectangles r_0 and r_1 intersect along the
x-axis. The z-values of the world_coordinates of r_0 are
-1.41421 and 1.41421 (two Points each), while those of r_1
are 2.12132 and -2.12132. So r_1 has two Points with
z-coordinates greater than the z-coordinate of any Point
on r_0, and two Points with z-coordinates less than the
z-coordinate of any Point on r_0. The
Points on r_0 and r_1 all have different z-values in
their projective_coordinates, but r_1 still has a Point
with a z-coordinate greater than that of any of the Points on
r_0, and one with a z-coordinate less than that of any of the
Points on r_0.
In [next figure]
, the Shapes on current_picture are sorted
according to the maximum z-values of the projective_coordinates
of the Points belonging to the Shapes. r_1 is
filled and drawn first,
because it has the Point with the positive z-coordinate of
greatest magnitude.
When subsequently r_0 is drawn, it covers part of the top of
r_1, which lies in front of r_0, and should be visible:
current_picture.output(default_focus, PERSP, 1, MAX_Z);
Fig. 68.
In [next figure]
, the Shapes on current_picture are sorted
according to the minimum z-values of the projective_coordinates
of the Points belonging to the Shapes. r1 is drawn
and filled last, because
it has the Point with the negative z-coordinate of greatest
magnitude.
It thereby covers the bottom part of
r0, which lies in front of r1, and should be visible.
current_picture.output(default_focus, PERSP, 1, MIN_Z);
Fig. 69.
Neither sorting by the mean z-value in the
projective_coordinates, nor suppressing sorting does any good.
In each case, one Rectangle is always drawn and filled last,
covering parts of the other that lie in front of the first.
3DLDF's hidden surface algorithm will fail wherever objects intersect, not just where one extends past the other in both the positive and negative z-directions.
Rectangle r(origin, 3, 4, 45);
Circle c(origin, 2, -45);
r.filldraw();
c.filldraw(black, gray);
current_picture.output(default_focus, PERSP, 1, NO_SORT);
Fig. 70.
Even where objects don't intersect, their projections may. In order to
handle these cases properly, it is necessary to break up the
Shapes on a Picture into smaller Shapes, until
there are none that intersect or whose projections intersect. Then, any
of the three methods of sorting described above can be used to sort the
Shapes, and they can be output.
Before this can be done, 3DLDF must be able to find the intersections of
all of the different kinds of Shapes. If 3DLDF converted solids
to polyhedra and curves to sequences of line segments, this would reduce
to the problem of finding the intersections of lines and planes, however
it does not yet do this.
Even if it did, a fully functional hidden surface algorithm must compare
each Shape on a Picture with every other Shape.
Therefore, for n Shapes, there will be
n! / ((n - r)! r!)
(possibly time-consuming) comparisons.
Fig. 71.
Clearly, such a hidden surface algorithm would considerably increase run-time.
Currently, all of the Shapes on a Picture are output, as
long as they lie completely within the boundaries passed as arguments to
Picture::output().
See Pictures; Outputting. It
would be more efficient to suppress output for them, if they are
completely covered by other objects. This also requires comparisions,
and could be implemented together with a fully-functional hidden surface
algorithm.
Shadows, reflections, highlights and shading are all effects requiring
comparing each Shape with every other Shape, and could
greatly increase run-time.
There are no functions for finding the intersection points of two (or
more) arbitrary Paths. This is impossible, so long as 3DLDF
outputs MetaPost code.
3DLDF only "knows" about the Points on a
Path; it doesn't actually generate the curve or other figure
that passes through the Points, and consequently doesn't "know"
how it does this.
In addition, an arbitrary Path can contain connectors.
In 3DLDF, the connectors are
merely strings and are written verbatim to the output file,
however, in MetaPost they influence the form of a Path.
3DLDF can, however, find the intersection points of some
non-arbitrary Paths. So far, it can find the intersection
point of the following combinations of Paths:
Paths, i.e., Paths
for which Path::is_linear() returns true
(see Path Reference; Querying).
In addition, the static Point member function
Point::intersection_points() can be called with four Point
arguments. The first and second arguments are treated as the end points
of one line, and the third and fourth arguments as the end points of the
other.
Polygon. Currently, Reg_Polygon and
Rectangle are the only classes derived from Polygon.
Polygons.
Reg_Cl_Plane_Curve,
see Regular Closed Plane Curve Reference; Intersections). Currently,
Ellipse and Circle are the only classes derived from
Reg_Cl_Plane_Curve.
Ellipses. Since a Circle is also an Ellipse,
one or both of the Ellipses may be a Circle.
See Ellipse Reference; Intersections.
Adding more functions for finding the intersections of various geometric figures is one of my main priorities with respect to extending 3DLDF.
There are currently no special
functions for finding the intersection points
of a line and a Circle or two Circles. Since the
class Circle is derived from class Ellipse,
Circle::intersection_points() resolves to
Ellipse::intersection_points(), which, in turn, calls
Reg_Cl_Plane_Curve::intersection_points().
This does the trick, but it's much easier to find the intersections for
Circles that it is for Ellipses. In particular, the
intersections of two coplanar Circles can be found
algebraically, whereas I've had to implement a numerical solution for
the case of two coplanar Ellipses with different centers and/or
axis orientation. It may also be worthwhile to write
a specialization for
finding the intersection points of a Circle and an
Ellipse.
The theory of intersections is a fascinating and non-trivial branch of
mathematics.23
As I learn more about it, I plan to define more
classes to represent various curves (two-dimensional ones to
start with) and functions for finding their intersection points.
3DLDF is available for downloading from
http://ftp.gnu.org/gnu/3dldf.
The official 3DLDF website is
http://www.gnu.org/software/3dldf.
The "tarball", i.e., the compressed archive file
3DLDF-1.1.5.1.tar.gz unpacks into a directory called
/3DLDF-1.1.5.1/.
On a typical Unix-like system, entering the following commands at the command line in a shell will unpack the 3DLDF distribution. Please note that the form of the commands may differ on your system.
gunzip 3DLDF-1.1.5.1.tar.gz
tar xpvf 3DLDF-1.1.5.1.tar
The p option to tar ensures that the files will have
the same permissions as when they were packed.
The directory 3DLDF-1.1.5.1/ contains a
configure script, which should
be called from the command line in the shell, using the absolute path of
3DLDF-1.1.5.1/ as the prefix argument. For example, if
the path is /usr/local/mydir/3DLDF-1.1.5.1/,
configure should be invoked as follows:
cd 3DLDF-1.1.5.1
configure --prefix=/usr/local/mydir/3DLDF-1.1.5.1/
configure generates a Makefile
from the Makefile.in in 3DLDF-1.1.5.1/, and
in each of the subdirectories 3DLDF-1.1.5.1/CWEB,
3DLDF-1.1.5.1/DOC,
and 3DLDF-1.1.5.1/DOC/TEXINFO.
Now, make install causes the 3DLDF to be built.
The executable is called 3dldf.
See the files README and INSTALL in the 3DLDF distribution
for more information.
3DLDF 1.1.5 is the first release that contains template functions,
namely
template <class C> C* create_new(), which is defined in
creatnew.web, and
template <class Real> Real get_second_largest(), which is defined
in gsltmplt.web.
See Dynamic Allocation of Shapes, and
Get Second Largest Real.
In order for template functions to be instantiated correctly, their
definitions must be available in each compilation unit where
specializations are declared or used. For non-template functions, it
suffices for their declarations to be available, and their
definitions are found at link-time. For this reason, the
definitions of create_new() and get_second_largest() are
in their own CWEB files, and are written to their own header files. The
latter are included in the other CWEB files that need them.
In addition, AM_CXXFLAGS = -frepo has been added to the file
Makefile.am in 3DLDF-1.1.5/CWEB/, so that the C++
compiler is called using the -frepo option.
The manual Using and Porting the GNU Compiler
Collection explains this as follows:
"Compile your template-using code with-frepo. The compiler will generate files with the extension.rpolisting all of the template instantiations used in the corresponding object files which could be instantiated there; the link wrapper,collect2, will then update the.rpofiles to tell the compiler where to place those instantiations and rebuild any affected object files. The link-time overhead is negligible after the first pass, as the compiler will continue to place the instantiations in the same files."24
The first time the executable 3dldf is built, the files that use
the template functions are recompiled one or more times, and the linker
is also called several times. This doesn't happen anymore, once the
.rpo files exist.
Template instantiation differs from compiler to compiler, so using template functions will tend to make 3DLDF less portable. I am no longer able to compile it on the DECalpha Personal Workstation I had been using with the DEC C++ compiler. See Ports, for more information.
To use 3DLDF, call
make run from the command line in the
shell. The working directory should be
3DLDF-1.1.5.1/ or 3DLDF-1.1.5.1/CWEB.
Either will work, but the latter may be more convenient, because
this is the location of the CWEB, TeX and MetaPost files that you'll
be editing.
Alternatively, call ldfr, which is merely a
shell script that calls make run.
This takes care of running 3dldf, MetaPost, TeX,
and dvips, producing a PostScript file containing your
drawings. You can display the latter on your terminal using Ghostview
or some other
PostScript viewer, print it out, and whatever else you like to do with
PostScript files.
However, you can also perform the actions performed by
make run by hand, by writing your own shell
scripts, by defining Emacs-Lisp commands, or in other ways. Even if you
choose to use make run, it's important to understand what it
does. The following explains how to do this by hand.
The CWEB source files for 3DLDF are in the subdirectory
3DLDF-1.1.5.1/CWEB/. They
must be ctangled, and the resulting C++
files must be
compiled and
linked, in order to create the executable file 3dldf.
The C++
files and header files generated by ctangle,
the object files generated by the compiler, and the executable
3dldf all reside in 3DLDF-1.1.5.1/CWEB/. Therefore, the
latter must be your working directory.
Since 3DLDF has no input routine as yet,
as explained in No Input Routine,
it is necessary to add C++
code to the function main() in
main.web, and/or in a separate function in another file. In the
latter case, the function containing the user code must be invoked in
main(). Look for the line "Your code here!" in
main.web.
This is an example of what you could write in main().
Feel free to make it more complicated, if you wish.
beginfig(1);
default_focus.set(2, 3, -10, 2, 3, 10, 20);
Rectangle R(origin, 5, 3);
Circle C(origin, 3, 90);
C.half(180).filldraw(black, light_gray);
R.filldraw();
C.half().filldraw(black, light_gray);
Point p = C.get_point(4);
p.shift(0, -.5 * p.get_y());
p.label("$C$", "");
Point q = R.get_mid_point(0);
q.shift(0, 0, -.5 * q.get_z());
q.label("$R$", "");
current_picture.output(default_focus, PERSP, 1, NO_SORT);
endfig(1);
Fig. 72.
main.web, and any other CWEB files you've changed.
Since these files have changed, they must be ctangled, and the
resulting C++
files must be recompiled. If you've changed any files
other than
main.web, ctangle will also generate a header
file for each of these files. If a header file differs from the version
that existed before ctangle was run, all of the C++
files
that depend on it must be recompiled. Then 3dldf must be
relinked. To do this, call make 3dldf from the command line.
If you've made any errors in typing your code, the
compiler should have issued error messages, so go back into
the appropriate CWEB file and correct your errors. Then call
make 3dldf again.
CWEB/3dldf at the command line. It writes a
file of MetaPost code called 3DLDFput.mp.
3DLDFmp.mp, which inputs
3DLDFput.mp.
mpost 3DLDFput
The result is an Encapsulated PostScript file
3DLDFput.<integer> for each figure in your drawing.
3DLDFtex.tex should contain code for including the
3DLDFput.<integer> files. This is an example taken from
the 3DLDFtex.tex included in the distribution.
You may change it to suit your purposes.
\vbox to \vsize{\vskip 2cm
\line{\hskip 2cm Figure 1.\hss}%
\vfil
\line{\hskip 2cm\epsffile{3DLDFmp.1}\hss}%
\vss}
3DLDFtex.tex to produce the DVI file,
3DLDFtex.dvi.
tex 3DLDFtex
dvips on the DVI file to produce the PostScript file,
3DLDFtex.ps.
dvips -o 3DLDFtex.ps 3DLDFtex
3DLDFtex.ps can be viewed using Ghostview, it can be printed using
lpr (on a Unix-like system), you can convert it to PDF with
ps2pdf, or to some other format using the appropriate program.
I sincerely hope that it worked. If it didn't, ask your local computer wizard for help.
On the computer I'm using, I found that special arguments for
setting landscape and papersize in TeX files for
DIN A3 landscape didn't work. Ghostview cut off the right sides of the
drawings. Nor did it work to call
dvips -t landscape -t a3.
This caused an error message which said that
landscape would be ignored. When I called dvips
with the -t landscape option alone, it worked, and
Ghostview showed the entire drawing.
Another problem was Adobe Acrobat. It would display the entire DIN A3 page, but not always in landscape format. I was unable to find a way of rotating the pages in Acrobat. I finally found out, that if I included even a single letter of text in a label, Acrobat would display the document correctly.
It is possible to have MetaPost generate structured PostScript directly
by including the command prologues:=1; at the beginning of the
MetaPost input.
However, this "generally doesn't work when you use TeX
fonts."25
This is a significant problem if your labels contain math mode
material, and you haven't already taken steps to ensure that appropriate
fonts will be used in the PS output.
In the following, I describe the only way I've found to convert an EPS image to PNG format while still using TeX fonts. There may be other and better ways of doing this, but I haven't found them.
3DLDFmp.1
Include the EPS image in a TeX file
which looks like this:
\advance\voffset by -1in
\advance\hoffset by -1in
\nopagenumbers
\input epsf
\epsfverbosetrue
\def\epsfsize#1#2{#1}
\setbox0=\vbox{\epsffile{3DLDFmp.1}}
\vsize=\ht0
\hsize=\wd0
\special{papersize=\the\wd0,\the\ht0}
\box0
\bye
Do not name this file 3DLDFmp.1.tex!
While this worked fine for me on a DECalpha Personal Workstation
running under Tru64 Unix 5.1, with TeX, Version 3.1415
(C version 6.1), and dvipsk 5.58f,
it failed on a PC Pentium II XEON under Linux 2.4,
with TeX, Version 3.14159 (Web2C 7.4.5), and
dvips(k) 5.92b, kpathsea version 3.4.5,
with the following error message:
``No BoundingBox comment found in file examples.1; using defaults''
The resulting PS image had the wrong size and the the graphic was positioned improperly.
Apparently, it confuses the EPSF macros when the name of an
included image is the same as \jobname.
So, for this example, let's call it 3DLDFmp.1_.tex.
You don't really need to call the macro \epsfverbosetrue. If you
do, it will print the measurements of the bounding box and other information
to standard output.26
tex 3DLDFmp.1_.tex.
dvips -o 3DLDF.1.ps 3DLDFmp.1_.dvi.
convert 3DLDF.1.ps 3DLDFmp.1.png.
display' utility, which can be used to display the
PNG image:
display 3DLDFmp.1.png
It can be included in an HTML document as follows:
<img src="3DLDFmp.1.png"
alt="[Fig. 1]."
Please note! The PNG files for this manual are now called
filename 3DLDF1.png, 3DLDF2.png, ...,
3DLDF199.png,
because I wasn't able to write files
with names like 3DLDFmp.<number>.png to a CD-R (Compact
Disk, Recordable), when `number' had more than one digit.
The file 3DLDF-1.1.5.1/CWEB/cnepspng.el contains
definitions of two Emacs-Lisp functions that can be used to
convert Encapsulated PostScript (EPS) files to structured PostScript
(PS) and Portable Network Graphics (PNG) files.
| convert-eps filename do-not-delete-files | Emacs-Lisp function |
|
Converts an EPS image file to the PS and PNG formats.
If called interactively, If do-not-delete-files is |
| convert-eps-loop arg start end | Emacs-Lisp function |
Converts a set of EPS image files to the PS and PNG formats.
The files
must all have the same filename, and the extensions must form a range of
positive integers. For example, convert-eps-loop can be
used to convert the files 3DLDFmp.1, 3DLDFmp.2, and
3DLDFmp.3 to 3DLDFmp.1.ps, 3DLDFmp.2.ps, and
3DLDFmp.3.ps on the one hand, and
3DLDFmp.1.png, 3DLDFmp.2.png,
3DLDFmp.3.png on the other.
If For all i \in \INT and start \le i \le end,
do-not-delete-files is also passed to |
3dldf can be called with the following
command line arguments.
--help
--silent
3dldf is run
--verbose
3dldf is run.
--version
Currently, 3dldf can only handle long options. -
cannot be substituted for --. However, the names of the options
themselves can be abbreviated, as long as the abbreviation is
unambigous. For example, 3dldf --h and 3dldf --verb are
valid, but 3dldf --ver is not.
3DLDF defines a number of data types for various reasons, e.g., for the
sake of convenience, for use in conditional compilation, or as return
values of functions. Some of these data types can be defined using
typedef, while others are defined as structs.
The typedefs and utility structures described in this chapter are
found in pspglb.web. Others, that contain objects of types
defined in 3DLDF, are described in subsequent chapters.
| real | typedef |
Synonymous either with float or double, depending on the
values of the preprocessor variables LDF_REAL_FLOAT and
LDF_REAL_DOUBLE. The meaning of real is determined by
means of conditional compilation. If real is float, 3DLDF
will require less memory than if real is double, but its
calculations will be less precise. real is "typedeffed" to
float by default.
|
| real_pair first second | typedef |
Synonymous with pair<real, real>.
|
| real_triple first second third | struct |
All three data elements of real_triple are reals.
It also has two constructors, described below. There are no other
member functions.
|
| void real_triple (void) | Constructor |
| void real_triple (real a, real b, real c) | Constructor |
The constructor taking no arguments sets first, second,
and third to 0. The constructor taking three real
arguments sets first to a, second to b, and
third to c.
|
| Matrix | typedef |
A Matrix is a 4 X 4
array of real, e.g.,
Matrix M; == real M[4][4].
It is used in class Transform for storing transformation
matrices. See Transforms, and See Transform Reference, for more
information.
|
| real_short first second | typedef |
Synonymous with pair<real, signed short>.
It is the return type of Plane::get_distance().
|
| bool_pair first second | typedef |
Synonymous with pair<bool, bool>.
|
| bool_real first second | typedef |
Synonymous with pair<bool, real>.
|
The global constants and variables described in this chapter are
found in pspglb.web. Others, of types
defined in 3DLDF, are described in subsequent chapters.
| bool ldf_real_float | Constants |
| bool ldf_real_double |
Set to 0 or 1 to match the values of the preprocessor macros
LDF_REAL_FLOAT and LDF_REAL_DOUBLE. The latter are used
for conditional compilation and determine whether real is
"typedeffed" to float or double, i.e., whether
real is made to be a synonym of float or double
using typedef.
|
| real PI | Constant |
The value of PI
is calculated as
4.0 * arctan(1.0).
I believe that a preprocessor macro "PI" was
available when I compiled 3DLDF using the DEC C++
compiler, and that
it wasn't, when I used GNU CC under Linux, but I'm no longer sure.
|
| valarray <real> null_coordinates | Variable |
Contains four elements, all 0. Used for resetting the sets of
coordinates belonging to Points, but only when the DEC C++
compiler is used. This doesn't work when GCC is used. |
| real INVALID_REAL | Constant |
Actually, INVALID_REAL is the largest possible real value
(i.e., float or double) on a given machine.
So, from the point of view of the compiler, it's not invalid at all.
However, 3DLDF uses it to indicate failure of some kind. For example,
the return value of a function returning real can be compared
with INVALID_REAL to check whether the function succeeded or
failed.
An alternative approach would be to use the exception handling facilities of C++ . I do use these, but only in a couple of places, so far. |
| real_pair INVALID_REAL_PAIR | Constant |
first and second are both INVALID_REAL.
|
| real INVALID_REAL_SHORT | Constant |
first is INVALID_REAL and second is 0.
|
| real MAX_REAL | Variable |
The largest real value permitted in the the elements of
Transforms and the coordinates of
Points. It is the second largest real value (i.e.,
float or double) on a given machine (INVALID_REAL
is the largest).
|
| real MAX_REAL_SQRT | Variable |
The square root of MAX_REAL.
Metafont implements an operation called Pythagorean addition,
notated as " |
template <class C> C* create_new (const C* arg)
|
Template function |
template <class C> C* create_new (const C& arg)
|
Template function |
These functions dynamically allocate an object derived from
Shape on the free store,
returning a pointer to the type of the Shape and setting
on_free_store to true.
If a non-zero pointer or a reference is passed to It is not possible to instantiate more than one specialization of
Point* p = create_new<Point>(0);
p->show("*p:");
-| *p: (0, 0, 0)
Color c(.3, .5, .25);
Color* d = create_new<Color>(c);
d->show("*d:");
-|
*d:
name ==
use_name == 0
red_part == 0.3
green_part == 0.5
blue_part == 0.25
Point a0(3, 2.5, 6);
Point a1(10, 11, 14);
Path q(a0, a1);
Path* r = create_new<Path>(&q);
r->show("*r:");
-|
*r:
points.size() == 2
connectors.size() == 1
(3, 2.5, 6) -- (10, 11, 14);
Specializations of this template function are currently declared for
|
The functions described in this chapter are all declared in the
namespace System. They are for finding out information
about the system on which 3DLDF is being run. They are declared and
defined in pspglb.web, except for the template function
get_second_largest(), which is declared and defined in
gsltmplt.web.
There are two reasons for this. The first is that template definitions
must be available
in the compilation units where specializations are instantiated.
I therefore write the template definition of get_second_largest()
to gsltmplt.h, so it can be included by the CWEB files that need
it, currently main.web only. If I
wrote it to pspglb.h, it would be included by all of the CWEB
files except for loader.web, causing unnecessarily bloated object
code.
The other reason is because of the way way 3DLDF is built using Automake
and make. I originally tried to define get_second_largest()
in pspglb.web and wrote the definition to gsltmplt.cc,
which is no problem with CWEB. However, I was unable to express the
dependencies among the CWEB, C++
, and object files in such a way that
3DLDF was built properly.
Therefore all template functions will be put into files either by themselves, or in small groups.
signed short get_endianness ([const bool verbose = false])
|
Function |
Returns the following values:
It is called by If verbose is This function has been adapted from Harbison, Samuel P., and Guy L. Steele Jr. C, A Reference Manual, pp. 163-164. This book has the clearest explanation of endianness that I've found so far. This is the C++ code: signed short
System::get_endianness(const bool verbose)
{
union {
long Long;
char Char[sizeof(long)];
} u;
u.Long = 1;
if (u.Char[0] == 1)
{
if (verbose)
cout << "Processor is little-endian."
<< endl << endl << flush;
return 0;
}
else if (u.Char[sizeof(long) - 1] == 1)
{
if (verbose)
cout << "Processor is big-endian."
<< endl << endl << flush;
return 1;
}
else
{
cerr << "ERROR! In System::get_endianness():\n"
<< "Can't determine endianness. Returning -1"
<< endl << endl << flush;
return -1;
}
}
|
bool is_big_endian ([const bool verbose = false])
|
Function |
Returns true if the processor is big-endian, otherwise false.
If verbose is true, messages are printed to standard
output.
|
bool is_little_endian ([const bool verbose = false])
|
Function |
Returns true if the processor is little-endian, otherwise false.
If verbose is true, messages are printed to standard
output.
|
| unsigned short get_register_width (void) | Function |
|
Returns the register width of the CPU of the system on which 3DLDF is
being run. This will normally be either 32 or 64 bits.
This is the C++ code: return (sizeof(void*) * CHAR_BIT);
This assumes that an address will be the same size as the processor's
registers, and that This function is called by |
| bool is_32_bit (void) | Function |
Returns true if the CPU of the system on which 3DLDF is being run
has a register width of 32 bits, otherwise false.
|
| bool is_64_bit (void) | Function |
Returns true if the CPU of the system on which 3DLDF is being run
has a register width of 64 bits, otherwise false.
|
template <class Real> Real get_second_largest (Real MAX_VAL, [bool verbose = false])
|
Template function |
| float get_second_largest (float, bool) | Template specialization |
| double get_second_largest (double, bool) | Template specialization |
get_second_largest returns the second largest floating point
number of the type specified the template paramater Real.
If verbose is true, messages are printed to standard
output.
This function is used for setting the value of
MAX_VAL should be the largest number of type Real on a given
architecture. The GNU C++
compiler GCC 3.3 does not currently supply
the |
Class Color is defined in colors.web.
| string name | Variable |
The name of the Color.
|
| bool use_name | Variable |
If true, name is written to out_stream when the
Color is used for drawing or filling. Otherwise, the
RGB (red-green-blue) values are written to out_stream.
|
| bool on_free_store | Variable |
true, if the Color has been created by
create_new<Color>(), which allocates memory for the
Color on the free store. Otherwise false.
Colors should only ever be dynamically allocated by using
create_new<Color>().
See Color Reference;;Constructors and Setting Functions.
|
| real red_part | Variable |
| real green_part | Variable |
| real blue_part | Variable |
The RGB (red-green-blue) values of the Color.
A real value r is valid for these variables if and
only if
0 <= r <= 1.
|
| void Color (void) | Default constructor |
Creates a Color and initializes its red_part,
green_part, and blue_part to 0. use_name and
on_free_store are set to false.
|
void Color (const Color& c, [const string n = "", [const bool u = true]])
|
Copy constructor |
Creates a Color and makes it a copy of c. If n is
not the empty string and u is true, use_name is set
to true. Otherwise, its set to false.
|
void Color (const string n, const unsigned short r, const unsigned short g, const unsigned short b, [const bool u = true])
|
Constructor |
Creates a Color with name n. Its red_part,
green_part, and blue_part are set to
r/255.0, g/255.0, and b/255.0,
respectively.
use_name is set to u.
|
void set (const string n, const unsigned short r, const unsigned short g, const unsigned short b, [const bool u = false])
|
Setting function |
Corresponds to the constructor above, except that u is false by default.
|
| void Color (const real r, const real g, const real b) | Constructor |
Creates an unnamed Color using the real values r,
g, and b for its red_part, green_part, and
blue_part, respectively.
|
| void set (const real r, const real g, const real b) | Setting function |
| Corresponds to the constructor above. |
| Color* create_new<Color> (const Color* c) | Template specializations |
| Color* create_new<Color> (const Color& c) |
Pseudo-constructors for dynamic allocation of Colors.
They create a Color on the free store and allocate memory for it using
new(Color). They return a pointer to the new Color.
If c is a non-zero pointer or a reference,
the new This function is used in the drawing and filling functions for
|
| void operator= (const Color& c) | Assignment operator |
Sets name to the empty string, use_name to
false, and red_part, green_part, and
blue_part to c.red_part, c.green_part, and
c.blue_part, respectively.
|
| bool operator== (const Color& c) | const operator |
Equality operator. Returns true, if the red_parts,
green_parts, and blue_parts of *this and c
are equal, otherwise false. The names and
use_names are not compared.
|
| bool operator!= (const Color& c) | const operator |
Inequality operator. Returns false, if the red_parts,
green_parts, and blue_parts of *this and c
are equal, otherwise true. The names and
use_names are not compared.
|
| ostream& operator<< (ostream& o, const Color& c) | Non-member function |
Output operator. Writes the MetaPost code for the Color to
out_stream when a Picture is output. This occurs when
the Color has been used as an argument to
drawing or filling functions.
If |
| void set_name (const string s) | Function |
Sets name to s. use_name is not reset.
|
| void set_use_name (const bool b) | Function |
Sets use_name to b.
|
| void modify (const real r, [const real g = 0, [const real b = 0]]) | Function |
Adds r, g, and b to red_part,
green_part, and blue_part, respectively. Following the
addition, if red_part, green_part, and/or blue_part
is greater than 1, it is reduced to 1. If it is less than 0, it is
increased to 0.
|
| void set_red_part (const real q) | Function |
| void set_green_part (const real q) | Function |
| void set_blue_part (const real q) | Function |
Let p stand for red_part,
green_part, or blue_part, depending upon which function is
used.
If
0 <= q <= 1,
p is set to q. If
q < 0, p is set to 0.
If q > 1, p is set to 1.
|
| void show ([string text = ""]) | const function |
Prints information about the Color to standard output.
If text is not the empty string, prints text on a
line of its own. Otherwise, it prints "Color:". Then it prints
name, use_name, red_part, green_part, and
blue_part.
|
| bool is_on_free_store (void) | const function |
Returns on_free_store. This will only be true, if the
Color was created by create_new<Color>().
See Color Reference; Constructors and Setting Functions.
|
real get_red_part ([bool decimal = false])
|
Inline const function |
real get_green_part ([bool decimal = false])
|
Inline const function |
real get_blue_part ([bool decimal = false])
|
Inline const function |
These functions return the red_part, green_part, or
blue_part of the Color, respectively. If decimal is
false (the default), the actual real value of the "part"
is returned. Otherwise, the corresponding whole number
n such that
0 <= n <= 255
is returned.
|
| bool get_use_name (void) | const function |
Returns use_name.
|
| string get_name (void) | Inline const function |
Returns name.
|
| void define_color_mp () | const function |
Writes MetaPost code to out_stream, in order to define objects of
type color within MetaPost, and set their redparts,
greenparts, and blueparts.
|
| void initialize_colors (void) | Static function |
Calls define_color_mp() (described above) for the
Colors that are defined in namespace Colors
(see Namespace Colors).
|
| const Color red | Constant |
| const Color green | Constant |
| const Color blue | Constant |
| const Color cyan | Constant |
| const Color yellow | Constant |
| const Color magenta | Constant |
| const Color orange_red | Constant |
| const Color violet_red | Constant |
| const Color pink | Constant |
| const Color green_yellow | Constant |
| const Color orange | Constant |
| const Color violet | Constant |
| const Color purple | Constant |
| const Color blue_violet | Constant |
| const Color yellow_green | Constant |
| const Color black | Constant |
| const Color white | Constant |
| const Color gray | Constant |
| const Color light_gray | Constant |
These constant Colors can be used in drawing and filling
commands.
|
| const Color default_background | Constant |
The default background color. Equal to white per default.
|
| const Color* background_color | Pointer |
Points to default_background by default.
|
| const Color* default_color | Pointer |
Points to black by default.
|
| const Color* help_color | Pointer |
Points to green by default.
|
The following vectors of pointers to Color can be used in the
drawing and filling functions for Solid
(see Solid Reference; Drawing and Filling).
| const vector <const Color*> default_color_vector | Vector |
Contains one pointer, namely default_color.
|
| const vector <const Color*> help_color_vector | Vector |
Contains one pointer, namely help_color.
|
| const vector <const Color*> background_color_vector | Vector |
Contains one pointer, namely background_color.
|
| ifstream in_stream | Variable |
Intended for inputting files of input code. However, 3DLDF does not
currently have a routine for reading input code.
in_stream is currently attached to the file ldfinput.ldf
by initialize_io() (see I/O Functions).
in_stream is read in character-by-character in main(),
however this serves no useful purpose as yet.
|
| ofstream out_stream | Variable |
Used for writing the file of MetaPost code, which is 3DLDF's output.
Currently attached to the file subpersp.mp by
initialize_io() (see I/O Functions).
|
| ofstream tex_stream | Variable |
TeX code can be written to a file through tex_stream, if
desired. 3DLDF makes no use of it itself.
Currently attached to subpersp.tex by
initialize_io() (see I/O Functions).
|
| void initialize_io (string in_stream_name, string out_stream_name, string tex_stream_name, char* program_name) | Function |
Opens files with names specified by the first three arguments, and
attaches them to the file streams in_stream, out_stream, and
tex_stream, respectively. Comments are written at the beginning
of the files, containing their names, a datestamp, and the name of the
program used to generate them.
|
| void write_footers (void) | Function |
Writes code at the end of the files attached to in_stream,
out_stream, and tex_stream, before the streams are
closed. Currently, they write comments containing
local variable lists
for use in
Emacs.
|
| void beginfig (unsigned short i) | Inline function |
Writes "beginfig(i)" to out_stream.
|
| void endfig ([unsigned short i = 0]) | Inline function |
Writes "endfig()" to out_stream. The argument i
is "syntactic sugar"; it's ignored by endfig(),
but may help the user keep track of what figure is being ended.
|
Class Shape is defined in shapes.web.
Shape is an abstract class, which means that
all of its member functions are pure virtual functions, and
that it's only used as a base class, i.e., no objects of type
Shape may be declared.
All of the "drawable" types in 3DLDF, Point,
Path, Ellipse, etc., are derived from Shape.
Deriving all of the drawable types from Shape makes it possible
to handle objects of different types in the same way. This is
especially important in the Picture functions, where objects of
various types (but all derived from Shape) are accessed through
pointers to Shape. See Picture Reference.
| signed short DRAWDOT | Protected static constants |
| signed short DRAW | |
| signed short FILL | |
| signed short FILLDRAW | |
| signed short UNDRAWDOT | |
| signed short UNDRAW | |
| signed short UNFILL | |
| signed short UNFILLDRAW |
Values used in the output() functions of the classes derived from
Shape. For example, in Path, if the data member
fill_draw_value = DRAW, then the MetaPost command
draw is written to out_stream when that Path is
output.
|
| Transform operator*= (const Transform& t) | Pure virtual function |
| Shape* get_copy (void) | const pure virtual function |
Copies an object, allocating memory on the free store for the copy,
and returns a pointer to Shape for accessing the copy.
Used in the drawing and filling functions for copying the |
bool set_on_free_store (bool b = true)
|
Pure virtual function |
Sets the data member on_free_store to b. All classes
derived from Shape must therefore also have a data member
on_free_store.
This function is used in the template function
|
| Transform rotate (const real x, const real y, const real z) | Pure virtual functions |
| Transform scale (real x, real y, real z) | |
| Transform shear (real xy, real xz, real yx, real yz, real zx, real zy) | |
| Transform shift (real x, real y, real z) | |
| Transform rotate (const Point& p0, const Point& p1, const real r) |
| See Point Reference; Affine Transformations. |
| void apply_transform (void) | Pure virtual function |
Applies the Transform stored in the transform data member
of the Points belonging to the Shape to their
world_coordinates. The transforms are subsequently reset
to the identity Transform.
|
| void clear (void) | Pure virtual function |
The precise definition of this function will depend on the nature of the
derived class. In general, it will call the destructor on dynamically
allocated objects belonging to the Shape, and deallocate the
memory they occupied.
|
| bool is_on_free_store (void) | const pure virtual function |
Returns true if the object was allocated on the free store,
otherwise false.
|
void show ([string text = "", [char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = 0, [const real factor = 1]]]]]]])
|
const pure virtual function |
Prints information about an object to standard output.
See the descriptions of show() for the classes derived from
Shape for more information.
|
| void output (void) | Pure virtual function |
Called by Picture::output() for writing MetaPost code to
out_stream for a Shape pointed to by a pointer on the
vector<Shape*> shapes belonging to the Picture. Such
a Shape will have been created by a drawing or filling
function.
|
| vector<Shape*> extract (const Focus& f, const unsigned short proj, real factor) | Pure virtual function |
Called in Picture::output(). It determines whether a
Shape can be output. If it can, and an output() function
for the type of the Shape exists, a vector<Shape*>
containing a pointer to the Shape is returned.
On the other
hand, it is possible to define a type derived from Currently, there are no |
| bool set_extremes (void) | Pure virtual function |
Sets the values of projective_extremes for the Shape.
This is needed in Picture::output() for determining the order in
which objects are output.
|
| real get_minimum_z (void) | const pure virtual functions |
| real get_maximum_z (void) | |
| real get_mean_z (void) |
These functions return the minimum, maximum, and mean z-value
respectively of the projected Points belonging to
the Shape, i.e., from projective_extremes. The values for
the Shapes on the Picture are used for determining the
order in which they are output
|
| const valarray<real> get_extremes (void) | const pure virtual function |
Returns projective_extremes.
|
| void suppress_output (void) | Pure virtual function |
Sets do_output to false. This function is called in
Picture::output(), if a Shape on a Picture cannot
be output using the arguments passed to Picture::output().
|
| void unsuppress_output (void) | Pure virtual function |
Sets do_output to true. Called in
Picture::output() after output() is called on the Shapes.
This way, output of Shapes that couldn't be output when
Picture::output() was called with a particular set of arguments
won't necessarily be suppressed when
Picture::output() is called again with different arguments.
|
f
Class Transform is defined in transfor.web.
Point is a friend of Transform.
| Matrix matrix | Private variable |
|
A 4 X 4
matrix of |
| Transform user_transform | Variable |
Currently has no function. It is intended to be used for transforming
the coordinates of Points between the world coordinate system
(WCS) and a user coordinate system (UCS), when routines for managing
user coordinate systems are implemented.
|
const Transform INVALID_TRANSFORM
|
Constant |
Every member of matrix in INVALID_TRANSFORM is
equal to INVALID_REAL.
|
const Transform IDENTITY_TRANSFORM
|
Constant |
Homogeneous coordinates and Transforms are unchanged by
multiplication with IDENTITY_TRANSFORM.
matrix is an identity matrix:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
See Transforms.
|
| void Transform (void) | Default constructor |
Creates a Transform containing the identity matrix.
|
| void Transform (real r) | Constructor |
Creates a Transform and sets all of the elements of matrix
to r. Currently, this
constructor is never used, but who knows? Maybe someday it will be
useful for something.
|
| void Transform (real r0_0, real r0_1, real r2, real r0_2, real r0_3, real r1_0, real r1_1, real r1_2, real r1_3, real r2_0, real r2_1, real r2_2, real r2_3, real r3_0, real r3_1, real r3_2, real r3_3) | Constructor |
Each of the sixteen real arguments is
assigned to the corresponding element of matrix:
matrix[0][0] = r0_0, matrix[0][1] = r0_1, etc.
Useful for specifying a transformation matrix completely.
|
| Transform operator= (const Transform& t) | Assignment operator |
| Sets *this to t and returns t. Returning *this would, of course, have exactly the same effect. |
| real operator*= (real r) | Operator |
Multiplication with assignment by a scalar.
This operator multiplies each element
E
of matrix by
the scalar r.
The return value is r. This makes it possible to
chain invocations of this function:
For a_x, b_x, c_x, ..., p_x in R
,
x in N
Transform T0(a_0, b_0, c_0, d_0,
e_0, f_0, g_0, h_0,
i_0, j_0, k_0 l_0,
m_0, n_0, o_0, p_0);
Transform T1(a_1, b_1, c_1, d_1,
e_1, f_1, g_1, h_1,
i_1, j_1, k_1 l_1,
m_1, n_1, o_1, p_1);
Transform T2(a_2, b_2, c_2, d_2,
e_2, f_2, g_2, h_2,
i_2, j_2, k_2 l_2,
m_2, n_2, o_2, p_2);
real r = 5;
Let M_0, M_1, and M_2 stand for
M_0 =
a_0 b_0 c_0 d_0
e_0 f_0 g_0 h_0
i_0 j_0 k_0 l_0
m_0 m_0 o_0 p_0
M_1 =
a_1 b_1 c_1 d_1
e_1 f_1 g_1 h_1
i_1 j_1 k_1 l_1
m_1 m_1 o_1 p_1
M_2 =
a_2 b_2 c_2 d_2
e_2 f_2 g_2 h_2
i_2 j_2 k_2 l_2
m_2 m_2 o_2 p_2
T0 *= T1 *= T2 *= r;
Now, M_0 =
5a_0 5b_0 5c_0 5d_0
5e_0 5f_0 5g_0 5h_0
5i_0 5j_0 5k_0 5l_0
5m_0 5m_0 5o_0 5p_0
M_1 =
5a_1 5b_1 5c_1 5d_1
5e_1 5f_1 5g_1 5h_1
5i_1 5j_1 5k_1 5l_1
5m_1 5m_1 5o_1 5p_1
M_2 =
5a_2 5b_2 5c_2 5d_2
5e_2 5f_2 5g_2 5h_2
5i_2 5j_2 5k_2 5l_2
5m_2 5m_2 5o_2 5p_2
This function is not currently used anywhere, but it may turn out to be useful for something. |
| Transform operator* (const real r) | const operator |
Multiplication of a Transform by a scalar without assignment.
The return value is a Transform
A.
If this.matrix has elements
E_T, then A.matrix has elements E_A such that
E_A = r * E_T
for all E. |
| Transform operator*= (const Transform& t) | Operator |
Performs matrix multiplication on matrix and
t.matrix. The result is assigned to matrix.
t is returned, not *this! This makes it possible to
chain invocations of this function:
Transform a;
a.shift(1, 1, 1);
Transform b;
b.rotate(0, 90);
Transform c;
c.shear(5, 4);
Transform d;
d.scale(3, 4, 5);
Let a_m, b_m, and c_m stand for
a_m =
1 0 0 0
0 1 0 0
0 0 1 0
1 1 1 1
b_m =
0.5 0.5 0.707 0
0.146 0.854 -0.5 0
-0.854 0.146 0.5 0
0 0 0 1
c_m =
1 12 14 0
10 1 15 0
11 13 1 0
0 0 0 1
d_m =
3 0 0 0
0 4 0 0
0 0 5 0
0 0 0 1
a *= b *= c *= d;a, b, and c are transformed by d, which
remains unchanged.
Now, a_m =
3 0 0 0
0 4 0 0
0 0 5 0
3 4 5 1
b_m =
1.5 2 3.54 0
-0.439 3.41 -2.5 0
-2.56 0.586 2.5 0
0 0 0 1
c_m =
3 48 70 0
30 4 75 0
33 52 5 0
0 0 0 1
d_m is unchanged.
|
| Transform operator* (const Transform t) | const operator |
Multiplication of a Transform by another Transform without
assignment.
The return value is a Transform whose matrix contains
values that are the result of the matrix multiplication of
matrix and t.matrix.
|
| Transform inverse (void) | const function |
Transform inverse ([bool assign = false])
|
Function |
Returns a Transform T with a T.matrix that is the
inverse of matrix. If assign==true, then
matrix is set to its inverse.
In the |
| void set_element (const unsigned short row, const unsigned short col, real r) | Function |
Sets the element of matrix indicated by the arguments to r.
Transform t;
t.set_element(0, 2, -3.45569);
t.show("t:");
-| t:
1 0 -3.46 0
0 1 0 0
0 0 1 0
0 0 0 1
|
| bool is_identity (void) | Function |
Returns true if *this is the identity Transform,
otherwise false. This function has both a const and a
non-const version. In the non-const version,
clean() is called on *this before comparing the elements of
matrix with 1 (for the main diagonal) and 0 (for the other
elements). In the const version, *this is copied,
clean() is called on the copy, and the elements of the copy's
matrix are compared with 0 and 1.
|
| real get_element (const unsigned short row, const unsigned short col) | const function |
Returns the value stored in the element of matrix indicated by the arguments.
Transform t;
t.shift(1, 2, 3);
t.scale(2.5, -1.2, 4);
t.rotate(30, 15, 60);
t.show("t:");
-| t:
1.21 2.09 0.647 0
0.822 -0.654 0.58 0
-2.18 0.224 3.35 0
-3.69 1.45 11.8 1
cout << t.get_element(2, 1);
-| 0.224
|
| real epsilon (void) | Static function |
Returns the positive real value of smallest magnitude
\epsilon which an element of a Transform should
contain. An element of a Transform may also contain
-\epsilon.
The value \epsilon is used for in the
function
Please note: I haven't tested whether 0.000000001 is a good
value yet, so users should be aware of this if they set Rotation causes a significant loss of precision to due to the use of the
|
| void show ([string text = ""]) | const function |
If the optional argument text is used, and is not the empty
string (""), text is printed on a line of its own to
the standard output first. Otherwise, "Transform:" is printed
on a line of its own to the standard output.
Then, the elements of matrix are printed to standard output.
Transform t;
t.show("t:");
-| t:
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
t.scale(1, 2, 3);
t.shift(1, 1, 1);
t.rotate(90, 90, 90);
t.show("t:");
-| t:
0 0 1 0
0 2 0 0
-3 0 0 0
-1 1 1 1
|
The affine transformation functions use their arguments to create a new
Transform t (local to the function) representing the
appropriate transformation. Then, *this is multiplied by t
and t is returned.
Returning t instead of *this makes it possible to put the
affine transformation function at the end of a chain of invocations of
Transform::operator*=():
Transform t0, t1, t2, t3;
...
t0 *= t1 *= t2 *= t3.scale(2, 3.5, 9);
t0, t1, and t2 are all multiplied by the
Transform with
matrix =
2 0 0 0
0 3.5 0 0
0 0 9 0
0 0 0 1
representing the scaling operation, not t3, which may
represent a combination of transformations.
| Transform scale (real x, [real y = 1, [real z = 1]]) | Function |
Creates a Transform t representing the scaling operation locally,
multiplies *this by t, and returns t.
A Transform representing scaling only, when applied to a
Point p, will cause its x-coordinate to be multiplied by
x, its y-coordinate to be multiplied by y, and its
z-coordinate to be multiplied by z.
=>
Transform t;
t.scale(12.5, 20, 1.3);
t.show("t:");
-| t:
12.5 0 0 0
0 20 0 0
0 0 1.3 0
0 0 0 1
|
| Transform shear (real xy, [real xz = 0, [real yx = 0, [real yz = 0, [real zx = 0, [real zy = 0]]]]]) | Function |
Creates a Transform t representing the shearing operation locally,
multiplies *this by t, and returns t.
When applied to a Point p(x,y,z);
Transform t;
t.shear(a, b, c, d, e, f);
p *= t;
=> p = ((x + ay + bz), (y + cx + dz), (z + ex + fy))
Transform t;
t.shear(2, 3, 4, 5, 6, 7);
t.show("t:");
-| t:
1 4 6 0
2 1 7 0
3 5 1 0
0 0 0 1
|
| Transform shift (real x, [real y = 0, [real z = 0]]) | Function |
| Transform shift (const Point& p) | Function |
|
These functions create a The version with the argument When a Point p(x,y,z);
Transform t;
t.shift(a, b, c);
p *= t;
=> p = (x + a, y + b, z + c)
|
| Transform shift_times (real x, [real y = 1, [real z = 1]]) | Function |
Multiplies the corresponding elements of matrix by
the real arguments, i.e.,
matrix[3][0] is multiplied by x,
matrix[3][1] is multiplied by y, and
matrix[3][2] is multiplied by z. Returns *this.
Ordinary shifting is additive, so a special function is needed to
multiply the elements of If the Transform t;
t.shift(1, 2, 3);
=>
t.shift_times(2, 2, 2);
=>
Rectangle r[4];
r[0].set(origin, 1, 1, 90);
r[3] = r[2] = r[1] = r[0];
Transform t;
t.shift(1.5, 1.5);
r[0] *= t;
r[0].draw();
t.shift_times(1.5, 1.5);
r[1] *= t;
r[1].draw();
t.shift_times(1.5, 1.5);
r[2] *= t;
r[2].draw();
t.shift_times(1.5, 1.5);
r[3] *= t;
r[3].draw();
Cuboid c(origin, 1, 1, 1);
c.draw();
Transform t;
t.rotate(30, 30, 30);
t.shift(1, 0, 1);
c *= t;
c.draw();
t.shift_times(1.5, 0, 1.5);
c *= t;
c.draw();
t.shift_times(1.5, 0, 1.5);
c *= t;
c.draw();
t.shift_times(1.5, 0, 1.5);
c *= t;
c.draw();
t.shift_times(1.5, 0, 1.5);
c *= t;
c.draw();
|
| Transform rotate (real x, [real y = 0, [real z = 0]]) | Function |
Rotation around the main axes.
Creates a Transform t representing the rotation,
multiplies *this by t, and returns t.
|
| Transform rotate (Point p0, Point p1, [const real angle = 180]) | Function |
Rotation around an arbitrary axis. The Point arguments represent
the end points of the axis, and angle is the angle of rotation.
Since 180 degrees
rotation is needed so often, 180 is the default for
angle.
|
| Transform rotate (const Path& p, [const real angle = 180]) | Function |
Rotation around an arbitrary axis. Path argument.
The Path p must be linear, i.e., p.is_linear() must
return true. See Path Reference; Querying.
|
| Transform align_with_axis (Point p0, Point p1, [char axis = 'z']) | Function |
Returns the Transform that would align the line through p0
and p1 with the major axis denoted by the axis argument.
The default is the z-axis. This function is used in the functions that
find intersections.
Point P0(1, 1, 1);
Point P1(2, 3, 4);
P0.draw(P1);
P0.dotlabel("$P_0$");
P1.dotlabel("$P_1$");
Transform t;
t.align_with_axis(P0, P1, 'z');
P0 *= P1 *= t;
t.show("t:");
-| t:
0.949 -0.169 0.267 0
0 0.845 0.535 0
-0.316 -0.507 0.802 0
-0.632 -0.169 -1.6 1
P0.show("P0:");
-| P0: (0, 0, 0)
P1.show("P1:");
-| P1: (0, 0, 3.74)
The following example shows how default_focus.set(2, 3, -10, 2, 3, 10, 10);
Circle c(origin, 3, 75, 25, 6);
c.shift(2, 3);
c.draw();
Point n = c.get_normal();
n.shift(c.get_center());
Transform t;
t.align_with_axis(c.get_center(), n, 'y');
t.show("t:");
-| t:
0.686 0.379 -0.621 0
0.543 0.3 0.784 0
0.483 -0.875 0 0
-3 -1.66 -1.11 1
n *= c *= t;
c.draw();
c.show("c:");
-| c:
fill_draw_value == 0
(1.31, 0, -0.728) .. (1.49, 0, -0.171) ..
(1.44, 0, 0.413) .. (1.17, 0, 0.933) ..
(0.728, 0, 1.31) .. (0.171, 0, 1.49) ..
(-0.413, 0, 1.44) .. (-0.933, 0, 1.17) ..
(-1.31, 0, 0.728) .. (-1.49, 0, 0.171) ..
(-1.44, 0, -0.413) .. (-1.17, 0, -0.933) ..
(-0.728, 0, -1.31) .. (-0.171, 0, -1.49) ..
(0.413, 0, -1.44) .. (0.933, 0, -1.17) .. cycle;
n.show("n:");
-| n: (0, 1, 0)
|
| void reset (void) | Function |
| Resets matrix to the identity matrix. |
| void clean (void) | Function |
Sets elements in matrix whose absolute values are
< epsilon() to 0.
|
Class Label is defined in pictures.web.
Point and Picture are friends of Label.
Labels can be included in drawings by using the label() and
dotlabel() functions, which are currently defined for the classes
Point and Path, and the classes derived from them.
See Point Reference; Labelling, and
See Path Reference; Labelling.
They are currently not defined for Solid, and its derived classes.
I plan to add them for Solid soon.
Users will normally
never need to declare objects of type Label, access its data
members or call its member functions directly.
When label() or dotlabel() is invoked, one or more Labels is
allocated dynamically and pointers to the new Labels are placed
onto the vector<Label*> labels of a Picture:
current_picture, by default. There are no explicitly defined
constructors for Label, nor is it intended that Labels
ever be created in any way other than through label() or
dotlabel(). When a Picture is copied, the Labels are
copied, too, and when a Picture is cleared (using
Picture::clear()) or destroyed, the Labels are deallocated
and destroyed.
| Point* pt | Private variable |
A pointer to the Point representing the location of the
Label.
|
| bool dot | Private variable |
true if the label should be dotted, otherwise
false.
|
| string text | Private variable |
The text of the label.
text is always put between "btex" and "etex" in
the MetaPost code, so that TeX will be used to format the labels. In
particular, this means that TeX's math mode can be used. However,
double backslashes must be used instead of single backslashes, in order
that single backslashes be written to out_stream.
Point P(1, 1, 2);
origin.drawarrow(P);
P.label("$\\vec{P}$");
|
| string position | Private variable |
The position of the text with respect to
*pt. Valid values are as in MetaPost:
"top", "bot" (bottom), "lft" (left), "rt"
(right), "ulft" (upper left),
"llft" (lower left), "urt" (upper right),
"lrt" (lower right).
|
| bool DO_LABELS | Public static variable |
Enables or disables creation
of Labels. If true, label
and dotlabel() cause Labels to be
created and put onto a Picture. If
false, they are not. Note that it is also
possible to suppress output of existing
Labels when outputting a Picture.
|
| Label* get_copy (void) | const Function |
Creates a copy of the Label and returns a pointer to the copy.
Called in Picture::operator=() and Picture::operator+=()
where Pictures are copied.
Users should never need to call this function directly.
See Picture Reference; Operators.
This function dynamically allocates a new
|
| void output (const Focus& f, const unsigned short proj, real factor, const Transform& t) | Function |
Writes MetaPost code for the labels to out_stream.
It is called in Picture::output()
(see Picture Reference; Outputting).
Users should never need to call this function directly.
When |
Class Picture is defined in pictures.web.
| Transform transform | Private variable |
Applied to the Shapes on the Picture when the latter is
output. It is initialized as the identity Transform, and can be
modified by the transformation functions, by
Picture::operator*=(const Transform&)
(see Picture Reference; Operators), and by
Picture::set_transform()
(see Picture Reference; Modifying).
|
| vector<Shape*> shapes | Private variable |
Contains pointers to the Shapes on the Picture.
When a drawing or filling function is invoked for a Shape, a copy
is dynamically allocated and a pointer to the copy is placed onto
shapes.
|
| vector<Label*> labels | Private variable |
Contains pointers to the Labels on the Picture. When a
Point is labelled, either directly or through a call to
label() or dotlabel() for another type of
Shape31,
a Label is dynamically allocated, the Point is copied to
*Label::pt, and a pointer to the Label is placed onto
labels.
|
| bool do_labels | Private variable |
Used for enabling or disabling output of Labels when outputting a
Picture. The default value is true. It is set to
false by using suppress_labels() and can be reset to
true by using unsuppress_labels().
See Picture Reference; Output Functions.
Often, when a |
| Variable Picture current_picture | Variable |
The Picture used as the default by the drawing and filling
functions.
|
| void Picture (void) | Default constructor |
Creates an empty Picture.
|
| void Picture (const Picture& p) | Copy constructor |
Creates a copy of Picture p.
Circle c(origin, 3);
c.draw();
current_picture.output(Projections::PARALLEL_X_Z);
Picture new_picture(current_picture);
new_picture.shift(2);
new_picture.output(Projections::PARALLEL_X_Z);
|
| void operator= (const Picture& p) | Assignment operator |
Makes *this a copy of p, destroying the old contents of *this.
|
| void operator+= (const Picture& p) | Operator |
Adds the contents of p to *this. p remains unchanged.
|
| void operator+= (Shape* s) | Operator |
Puts s onto shapes. Note that the pointer s
itself is put onto shapes, so any allocation and copying must be
performed first. This is a low-level function that users normally won't
need to use directly.
|
| void operator+= (Label* label) | Operator |
Puts label onto labels.
Note that the pointer label
itself is put onto labels, so any allocation and copying must be
performed first. This is a low-level function that users normally won't
need to invoke directly.
|
| Transform operator*= (const Transform& t) | Operator |
Multiplies transform by t. This has the effect of
transforming all of the Shapes on shapes and all of
the Points of the Labels on labels by t upon
output.
Transform t;
t.rotate(0, 0, 180);
t.shift(3);
Reg_Polygon pl(origin, 5, 3, 90);
pl.draw();
pl.label();
current_picture.output(Projections::PARALLEL_X_Y);
current_picture *= t;
current_picture.output(Projections::PARALLEL_X_Y);
|
The functions in this section all operate on the transform data
member of the Picture and return a Transform representing the
transformation--not transform.
| Transform scale (real x, [real y = 1, [real z = 1]]) | Function |
Performs transform.scale(x, y, z) and returns
the result. This has the effect of scaling
all of the elements of shapes and labels.
|
| Transform shift (real x, [real y = 0, [real z = 0]]) | Function |
Performs transform.shift(x, y, z) and returns
the result. This has the effect of shifting
all of the Shapes and Labels on the Picture.
|
| Transform shift (const Point& p) | Function |
Performs transform.shift(p) and returns
the result. This has the effect of shifting
all of the Shapes and Labels on the Picture by the
x, y, and z-coordinates of p.
|
| Transform rotate (const real x, [const real y = 0, [const real z = 0]]) | Function |
Performs transform.rotate(x, y, z) and returns
the result. This has the effect of rotating
all of the elements of shapes and labels.
|
| Transform rotate (const Point& p0, const Point& p1, [const real angle = 180]); | Function |
Performs transform.rotate(p0, p1, angle) and returns
the result. This has the effect of rotating
all of the elements of shapes and labels about the line
from p_0 to p_1.
|
| void clear (void) | Function |
Destroys the Shapes and Labels on the
Picture and removes all the Shape pointers from
shapes and the Label pointers from labels.
All dynamically allocated objects are deallocated, namely the
Shapes, the Labels, and the Points belonging to the
Labels. transform is reset to the identity Transform.
|
| void reset_transform (void) | Function |
Resets transform to the identity Transform.
|
| Transform set_transform (const Transform& t) | Function |
Sets transform to t and returns t.
|
| void kill_labels (void) | Function |
Removes the Labels from the Picture.
|
void show ([string text = "", [bool stop = false]])
|
Function |
Prints information about the Picture to standard output.
|
| void show_transform ([string text = "Transform from Picture:"]) | Function |
Calls transform.show(), passing text as the argument to the
latter function.
|
The namespace Projections is defined in pictures.web.
| const unsigned short PERSP | Constant |
| const unsigned short PARALLEL_X_Y | Constant |
| const unsigned short PARALLEL_X_Z | Constant |
| const unsigned short PARALLEL_Z_Y | Constant |
| const unsigned short AXON | Constant |
| const unsigned short ISO | Constant |
These constants can be used for the projection argument in
Picture::output(), described in
Picture Reference; Outputting; Functions,
below.
|
The namespace Sorting is defined in pictures.web.
| const unsigned short NO_SORT | Constant |
| const unsigned short MAX_Z | Constant |
| const unsigned short MIN_Z | Constant |
| const unsigned short MEAN_Z | Constant |
These constants can be used for the sort_value argument in
Picture::output(), described in
Picture Reference; Outputting; Functions,
below.
|
void output (const Focus& f, [const unsigned short projection = Projections::PERSP, [real factor = 1, [const unsigned short sort_value = Sorting::MAX_Z, [const bool do_warnings = true, [const real min_x_proj = -40, [const real max_x_proj = 40, [const real min_y_proj = -40, [const real max_y_proj = 40, [const real min_z_proj = -40, [const real max_z_proj = 40]]]]]]]]]])
|
Function |
void output ([const unsigned short projection = Projections::PERSP, [real factor = 1, [const unsigned short sort_value = Sorting::MAX_Z, [const bool do_warnings = true, [const real min_x_proj = -40, [const real max_x_proj = 40, [const real min_y_proj = -40, [const real max_y_proj = 40, [const real min_z_proj = -40, [const real max_z_proj = 40]]]]]]]]]])
|
Function |
These functions create a two-dimensional projection of the objects on the
Picture and write MetaPost code to out_stream for
drawing it.
The arguments:
|
| void suppress_labels (void) | Function |
Suppresses output of the Labels on a Picture when
output() is called. This can be useful when a Picture is
output, transformed, and output again, one or more times, in a single figure.
Usually, it will not be desirable to have the Labels output more
than once.
In [next figure]
, Ellipse e(origin, 3, 5);
e.label();
e.draw();
Point pt0(-3);
Point pt1(3);
pt0.draw(pt1);
Point pt2(0, 0, -4);
Point pt3(0, 0, 4);
pt2.draw(pt3);
pt0.dotlabel("0", "lft");
pt1.dotlabel("1", "rt");
pt2.dotlabel("2", "bot");
pt3.dotlabel("3");
current_picture.output(Projections::PARALLEL_X_Z);
current_picture.rotate(0, 60);
current_picture.suppress_labels();
current_picture.output(Projections::PARALLEL_X_Z);
current_picture.rotate(0, 60);
current_picture.output(Projections::PARALLEL_X_Z);
|
| void unsuppress_labels (void) | Inline function |
Sets do_labels to true. If a Picture contains
Labels, unsuppress_labels() ensures that they will be
output, when Picture::output() is called, so long as there is no
intervening call to suppress_labels() or kill_labels().
|
Class Point is defined in points.web.
It is derived from Shape using protected derivation.
The function
Transform Transform::align_with_axis(Point, Point, char)
is a friend of Point.
| valarray<real> world_coordinates | Private variable |
The set of four homogeneous coordinates x, y, z, and w that represent
the position of the Point within 3DLDF's global coordinate
system.
|
| valarray<real> projective_coordinates | Private variable |
The set of four homogeneous coordinates x, y, z, and w that represent
the position of the projection of the Point onto a
two-dimensional plane for output. The x and y values are used in the
MetaPost code written to out_stream. The z value is used
in the hidden surface algorithm (which is currently rather primitive and
doesn't work very well. see Surface Hiding). The w value can be
!= 1
,
depending on the projection used; the perspective projection is
non-affine, so w can take on other values.
|
| valarray<real> user_coordinates | Private variable |
|
A set of four homogeneous coordinates x, y, z, and w.
|
| valarray<real> view_coordinates | Private variable |
|
A set of four homogeneous coordinates x, y, z, and w.
|
| Transform transform | Private variable |
Contains the product of the transformations applied to the Point.
When apply_transform() is called for the Point, directly
or indirectly, the world_coordinates are updated and
transform is reset to the identity Transform.
See Point Reference; Applying Transformations.
|
| bool on_free_store | Private variable |
Returns on_free_store. This should only be true if
the Point was dynamically allocated on the
free store. Points should only ever be dynamically
allocated by create_new<Point>(), which
uses set_on_free_store() to set on_free_store
to true.
See Point Reference; Constructors and Setting Functions, and
Point Reference; Modifying.
|
| signed short drawdot_value | Private variable |
Used to tell Point::output() what MetaPost drawing command
(drawdot() or undrawdot()) to write to out_stream
when outputting a Point.
When |
| const Color* drawdot_color | Private variable |
Used to tell Point::output() what string to write to out_stream
for the color when outputting a Point.
|
| string pen | Private variable |
Used to tell Point::output() what string to write to out_stream
for the pen when outputting a Point.
|
| valarray<real> projective_extremes | Protected variable |
A set of 6 real values indicating the maximum and minumum x, y,
and
z-coordinates of the Point.
Used for determining whether a Point is projectable with the
parameters of a particular invocation of Picture::output().
See Picture Reference; Outputting.
Obviously, the maxima and minima
will always be the same for a |
| bool do_output | Protected variable |
true by default. Set to false by suppress_output(),
which is called on a Shape by Picture::output(), if the
Shape is not projectable.
See Picture Reference; Outputting.
|
| string measurement_units | Public static variable |
The unit of measurement for all distances within a Picture,
"cm" (for centimeters) by default. The x and y-coordinates of
the projected Points are always followed by measurement_units
when they're written to out_stream. Unlike Metafont, units of
measurement cannot be indicated for individual coordinates. Nor can
measurement_unit be changed within a Picture.
When I write an input routine, I plan to make it behave the way Metafont does, however, 3DLDF will probably also convert all of the input values to a standard unit, as Metafont does. |
| real CURR_Y | Public static variable |
| real CURR_Z | Public static variable |
Default values for the y and z-coordinate of Points, when the
x-coordinate, or the x and y-coordinates only are specified.
Both are 0 by default.
These values only used in the constructor and setting function taking
one required Point A(1);
A.show("A:");
-| A: (1, 0, 0);
CURR_Y = 5;
A.set(2);
A.show("A:");
-| A: (2, 5, 0);
CURR_Z = 12;
Point B(3);
B.show("B:");
-| B: (3, 5, 12);
Point C;
C.show("C:");
-| C: (0, 0, 0);
|
| point_pair first second | typedef |
Synonymous with pair<Point, Point>.
|
| bool_point b pt | struct |
b is a bool and pt is a Point.
bool_point also contains two constructors and an assignment
operator, described below.
|
| void bool_point (void) | Default constructor |
Creates a bool_point and sets b to false and
pt to INVALID_POINT.
|
| void bool_point (bool bb, const Point& ppt) | Default constructor |
Creates a bool_point and sets b to bb and pt
to ppt.
|
| void bool_point::operator= (const bool_point& bp) | Assignment operator |
Sets b to
bp.b and pt to bp.pt.
|
| bool_point_pair first second | typedef |
Synonymous with pair <bool_point, bool_point>.
|
| bool_point_quadruple first second third fourth | struct |
This structure contains four bool_points. It also has two
constructors and an assignment operator, described below.
|
| void bool_point_quadruple (void) | Default constructor |
Creates a bool_point_quadruple, and sets
first, second, third, and fourth all to
INVALID_BOOL_POINT.
|
| void bool_point_quadruple (bool_point a, bool_point b, bool_point c, bool_point d) | Constructor |
Creates a bool_point_quadruple and sets
first to a, second to b, third to
c, and fourth to d.
|
| void bool_point_quadruple::operator= (const bool_point_quadruple& arg) | Assignment operator |
Makes *this a copy of arg.
|
| bool_real_point b r pt | struct |
b is a bool, r is a real, and pt is a
Point. bool_real_point also contains three constructors
and an assignment operator, described below.
|
| void bool_real_point (void) | Default constructor |
Creates a bool_real_point and sets b to false,
r to INVALID_REAL and pt to INVALID_POINT.
|
| void bool_real_point (const bool_real_point& brp) | Copy constructor |
Creates a bool_real_point and sets b to brp.b,
r to brp.r, and pt to brp.pt.
|
| void bool_real_point (const bool& bb, const real& rr, const Point& ppt) | Constructor |
Creates a bool_real_point and sets b to bb,
r to rr, and pt to ppt.
|
| void bool_real_point::operator= (const bool_real_point& brp) | Assignment operator |
Makes *this a copy of brp.
|
| Point INVALID_POINT | Constant |
The x, y, and z-values in world_coordinates are all INVALID_REAL.
|
| Point origin | Constant |
The x, y, and z-values in world_coordinates are all 0.
|
| bool_point INVALID_BOOL_POINT | Constant |
b is false and pt is INVALID_POINT.
|
| bool_point_pair INVALID_BOOL_POINT_PAIR | Constant |
first and second are both INVALID_BOOL_POINT.
|
| bool_real_point INVALID_BOOL_REAL_POINT | Constant |
b is false, r is INVALID_REAL, and pt
is INVALID_POINT.
|
| bool_point_quadruple INVALID_BOOL_POINT_QUADRUPLE | Constant |
first, second, third, and fourth are all
INVALID_BOOL_POINT.
|
| void Point (void) | Default constructor |
Creates a Point and initializes its x, y, and z-coordinates
to 0.
|
void Point (const real x, [const real y = CURR_Y, [const real z = CURR_Z]])
|
Constructor |
Creates a Point and initializes its x, y, and z-coordinates
to the values of the arguments x, y, and z. The
arguments y and z are optional. If they are not specified,
the values of CURR_Y and CURR_Z are used. They are 0 by
default, but can be changed by the user. This can be convenient, if all
of the Points being drawn in a particular section of a program
have the same z or y and z values.
|
void set (const real x, [const real y = CURR_Y, [const real z = CURR_Z]])
|
Setting function |
Corresponds to the constructor above, but is used for resetting the coordinates of an existing
Point.
|
| void Point (const Point& p) | Copy constructor |
Creates a Point and copies the values for its x, y, and z-coordinates
from p.
|
| void set (const Point& p) | Setting function |
Corresponds to the copy constructor above, but is used for resetting the coordinates
of an existing Point. This function exists purely as a convenience;
the operator operator=()
(see Point Reference; Operators)
performs exactly the
same function.
|
| Point* create_new<Point> (const Point* p) | Template specializations |
| Point* create_new<Point> (const Point& p) |
Pseudo-constructors for dynamic allocation of Points.
They create a Point on the free store and allocate memory for it using
new(Point). They return a pointer to the new Point.
If p is a non-zero pointer or a reference,
the new One use for Programmers who dynamically allocate |
void ~Point (void)
|
virtual Destructor |
| This function currently has an empty definition, but its existence prevents GCC 3.3 from issuing the following warning: "`class Point' has virtual functions but non-virtual destructor". |
| void operator= (const Point& p) | Assignment operator |
Makes *this a copy of p.
|
| Transform operator*= (const Transform& t) | Operator |
Multiplies transform by t.
By multiplying a Point successively by
one or more Transforms, the effect of the transformations is
"saved up" in transform. Only when an operation that needs
updated values for the world_coordinates is called on a
Point, or the Point is passed as an argument to such an
operation, is the transformation stored in transform applied to
world_coordinates by apply_transform(),
which subsequently, resets transform to
the identity Transform.
See Point Reference; Applying Transformations.
|
| Point operator+ (Point p) | const operator |
Returns a Point with world_coordinates that are the sums of
the corresponding world_coordinates of *this and p,
after they've been updated.
*this remains unchanged; as in many other functions with
Point arguments, p is passed by value, because
apply_transform() must be called on it, in order to update its
world_coordinates. If p were a const Point&, it
would have to copied within the function anyway, because
apply_transform() is a non-const operation.
Point p0(-2, -6, -28);
Point p1(3, 14, 92);
Point p2(p0 + p1);
p2.show("p2:");
-| p2: (1, 8, 64)
|
| void operator+= (Point p) | Operator |
Adds the updated world_coordinates of p to those of
*this.
Equivalent in effect to shift(p)
In fact, this
function merely calls p.apply_transform() and
Point::shift(real, real, real) with p's x, y, and z
coordinates (from world_coordinates) as its arguments.
See Point Reference; Affine Transformations.
|
| Point operator- (Point p) | const operator |
Returns a Point with world_coordinates representing the
difference between the updated values of
this->world_coordinates and
p.world_coordinates.
|
| void operator-= (Point p) | Operator |
Subtracts the updated values of p.world_coordinates from
those of this->world_coordinates.
|
| real operator*= (const real r) | Operator |
Multiplies the updated x, y, and z coordinates (world_coordinates) of
the Point by r and returns r. This makes it possible to
chain invocations of this function.
If Point P(1, 2, 3);
P *= 7;
P.show("P:");
-| P: (7, 14, 21);
Point Q(1.5, 2.7, 13.82);
Q *= P *= -1.28;
P.show("P:");
-| P: (-8.96, -17.92, -26.88)
Q.show("Q:");
-| Q: (-1.92, -3.456, -17.6896)
|
| Point operator* (const real r) | const operator |
Returns a Point with x, y, and z coordinates
(world_coordinates) equal to the updated x, y, and z coordinates
of *this multiplied by r.
|
| Point operator* (const real r, const Point& p) | Non-member operator |
Equivalent to Point::operator*(const real r) (see above),
but with r placed first.
Point p0(10, 11, 12);
real r = 2.5;
Point p1 = r * p0;
p1.show();
-|Point:
-|(25, 27.5, 30)
|
| Point operator- (void) | const operator |
Unary minus (prefix). Returns a Point with x, y, and z
coordinates (world_coordinates) equal to the
the x, y, and z-coordinates (world_coordinates) of
*this multiplied by -1.
|
| void operator/= (const real r) | Operator |
Divides the updated x, y, and z coordinates (world_coordinates) of
the Point by r.
|
| Point operator/ (const real r) | const operator |
Returns a Point with x, y, and z coordinates
(world_coordinates) equal to the updated x, y, and z coordinates
of *this
divided by r.
|
| bool operator== (Point p) | Operator |
| bool operator== (const Point& p) | const operator |
Equality comparison for Points. These functions return
true if the updated values of the world_coordinates of the two
Points differ by less than the value returned by
Point::epsilon(), otherwise false.
See Point Reference; Returning Information.
|
| bool operator!= (const Point& p) | const operator |
Inequality comparison for Points. Returns false if
*this == p, otherwise true.
|
| Shape* get_copy (void) | const function |
Creates a copy of the Point, and allocates memory for it on the
free store using create_new<Point>().
It returns a pointer to Shape that points to the new Point.
This function is used in the drawing commands for putting Points
onto Pictures.
See Point Reference; Drawing.
|
| bool is_identity (void) | inline function |
Returns true if transform is the identity Transform.
|
| Transform get_transform (void) | const inline function |
Returns transform.
|
| bool is_on_free_store (void) | const function |
Returns true if memory for the Point has been dynamically
allocated on the
free store, i.e., if the Point has been created using
create_new<Point>().
See Point Reference; Constructors and Setting Functions.
|
| bool is_on_plane (const Plane& p) | const function |
Returns true, if the Point lies on the
Plane p, otherwise false.
Planes are conceived of as having infinite extension, so while
the Point P(1, 1, 1);
Rectangle r(P, 4, 4, 20, 45, 35);
Plane q = r.get_plane();
Point A(2, 0, 2);
Point B(2, 1.64143, 2);
Point C(0.355028, 2.2185, 6.48628);
cout << A.is_on_plane(q);
-| 0
cout << B.is_on_plane(q);
-| 1
cout << "C.is_on_plane(q)";
-| 1
|
bool is_in_triangle (const Point& p0, const Point& p1, const Point& p2, [bool verbose = false, [bool test_points = true]])
|
const function |
Returns true, if *this lies within the triangle determined by
the three Point arguments, otherwise false.
If the code calling If the verbose argument is This function is needed for determining whether a line intersects with a polygon. |
The functions in this section return either a single coordinate or a set of
coordinates. Each has
a const and a non-const version.
The arguments are the same, with one exception:
char c
get_coord(). Indicates which coordinate should be
returned. Valid values are 'x', 'X', 'y',
'Y', 'z', 'Z', 'w', and 'W'.
char coords
'w' for world_coordinates (the default), 'p' for
projective_coordinates, 'u' for user_coordinates,
and 'v' for view_coordinates.
const bool do_persp
projective_coordinates, or one of its elements
is to be returned. If true, the
default, then project() is called, thereby generating values for
projective_coordinates. If do_persp is false, then
projective_coordinates, or one of its elements, is
returned unchanged, which may sometimes be useful.
const bool do_apply
true (the default), apply_transform() is called,
thereby updating the world_coordinates. Otherwise, it's not, so
that the values stored in world_coordinates remain unchanged.
Note that if coords is 'p' and do_persp is true,
apply_transform() will be called in project()
whether do_apply is true or false. If for some
reason, one wanted get projective_coordinates, or one of its
values, based on the
projection of world_coordinates without first updating them, one
would have to call reset_transform() before calling one of
these functions. It would probably be a good idea to save transform
before doing so.
Focus* f
Focus is to be used for projection.
Only relevant if coords is 'p', i.e.,
projective_coordinates, or one of its elements, is to be
returned. The default is 0, in which case f points to the global
variable default_focus.
const unsigned short proj
coords is 'p', i.e.,
projective_coordinates, or one of its elements, is to be
returned. The default is Projections::PERSP, which causes the
perspective projection to be applied.
real factor
project(). The values of the x and y coordinates in
projective_coordinates are multiplied by factor.
Only relevant if coords is 'p', i.e.,
projective_coordinates, or one of its elements, is to be
returned. The default is 1.
| valarray <real> get_all_coords ([char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]]) | Function |
Returns one of the sets of coordinates; world_coordinates by
default.
Returns a complete set of coordinates: 'w' for world_coordinates,
'p' for projective_coordinates, 'u' for
user_coordinates, or'v' for view_coordinates.
|
real get_coord (char c, [char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]])
|
Function |
Returns one coordinate, x, y, z, or w, from the set of
coordinates indicated (or world_coordinates, by default).
|
| real get_x ([char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]]) | Function |
Returns the x-coordinate from the set of coordinates indicated (or
world_coordinates, by default).
|
| real get_y ([char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]]) | Function |
Returns the y-coordinate from the set of coordinates indicated (or
world_coordinates, by default).
|
| real get_z ([char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]]) | Function |
Returns the z-coordinate from the set of coordinates indicated (or
world_coordinates, by default).
|
| real get_w ([char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::PERSP, [real factor = 1]]]]]]) | Function |
Returns the w-coordinate from the set of coordinates indicated (or
world_coordinates, by default).
|
| real epsilon (void) | Static function |
Returns the positive real value of smallest magnitude
\epsilon that should be used as a coordinate value in a
Point.
A coordinate of a Point may also contain
-\epsilon.
The value \epsilon is used for testing the equality of
Let \epsilon be the value returned by
Please note: I haven't tested whether 0.000000001 is a good
value yet, so users should be aware of this if they set Rotation causes a significant loss of precision to due to the use of the
|
| bool set_on_free_store ([bool b = true]) | Virtual function |
This function is used in the template function
create_new(). It sets on_free_store to true.
See Point Reference; Data Members, and
Point Reference; Constructors and Setting Functions.
|
| void clear (void) | Function |
Sets all of the coordinates in all of the sets of coordinates (i.e.,
world_coordinates, user_coordinates, view_coordinates, and
projective_coordinates) to 0 and resets transform
|
| void clean ([int factor = 1]) | Function |
Calls apply_transform() and sets the values of
world_coordinates to 0, whose absolute values are less than
epsilon() * factor
.
|
| void reset_transform (void) | Function |
Sets Transform to the identity Transform. Performed in
apply_transform(), after the latter updates world_coordinates.
Point Reference; Applying Transformations.
|
| Transform rotate (const real x, [const real y = 0, [const real z = 0]]) | Function |
| Transform rotate (const Point& p0, const Point& p1, [const real angle = 180]) | Function |
| Transform rotate (const Path& p, [const real angle = 180]) | Function |
Each of these functions calls the corresponding version of
Transform::rotate(), and returns its
return value, namely, a Transform representing the rotation
only.
In the first version, taking three Point p0(1, 0, 2);
p0.rotate(90);
p0.show("p0:")
-| p0: (1, 2, 0)
Point p1(-1, 1, 1);
p1.rotate(-90, 90, 90);
p1.show("pt1:");
-| p1: (1, -1, -1)
Please note that rotations are not commutative operations. Nor are they
commutative with other transformations.
So, if you want to rotate a Point pt0(1, 1, 1);
pt0.rotate(0, 45);
pt0.rotate(45);
pt0.show("pt0:");
-| pt0: (0, 1.70711, 0.292893)
In the version taking two Point P(2, 0, 0);
Point A;
Point B(2, 2, 2);
P.rotate(A, B, 180);
|
| Transform scale (real x, [real y = 1, [real z = 1]]) | Function |
Calls transform.scale(x, y, z) and returns its
return value, namely, a Transform representing the scaling operation
only.
Scaling causes the x-coordinate of the Point p0(1, 0, 3);
p0.scale(4);
p0.show("p0:");
-| p0: (4, 0, 3)
Point p1(-2, -1, -2);
p1.scale(-2, -3, -4);
p1.show("p1:");
-| p1: (4, 3, 8)
|
| Transform shear (real xy, [real xz = 0, [real yx = 0, [real yz = 0, [real zx = 0, [real zy = 0]]]]]) | Function |
Calls transform.shear() with the same arguments
and returns its
return value, namely, a Transform representing the shearing operation
only.
Shearing modifies each coordinate of a x_1 == x_0 + \alpha y + \beta z
y_1 == y_0 + \gamma x + \delta z
z_1 == z_0 + \epsilon x + \zeta y
[next figure]
demonstrates the effect of shearing the four
Point P0;
Point P1(3);
Point P2(3, 3);
Point P3(0, 3);
Rectangle r(p0, p1, p2, p3);
r.draw();
r.shear(1.5);
r.draw(black, "evenly");
|
| Transform shift (real x, [real y = 0, [real z = 0]]) | Function |
| Transform shift (const Point& p) | Function |
Each of these functions calls the corresponding version of
Transform::shift() on transform, and returns its return
value, namely, a Transform representing the shifting operation
only.
The p0(1, 2, 3);
p0.shift(2, 3, 5);
p0.show("p0:");
-| p0: (3, 5, 8)
|
| Transform shift_times (real x, [real y = 1, [real z = 1]]) | Function |
| Transform shift_times (const Point& p) | Function |
Each of these functions calls the corresponding version of
Transform::shift_times() on transform and
returns its return value, namely the new value of transform.
Point P;
P.drawdot();
P.shift(1, 1, 1);
P.drawdot();
P.shift_times(2, 2, 2);
P.drawdot();
P.shift_times(2, 2, 2);
P.drawdot();
P.shift_times(2, 2, 2);
P.drawdot();
|
| void apply_transform (void) | Function |
Updates world_coordinates by multiplying it by transform,
which is subsequently reset to the identity Transform.
|
| bool project (const Focus& f, [const unsigned short proj = Projections::PERSP, [real factor = 1]]) | Function |
| bool project ([const unsigned short& proj = Projections::PERSP, [real factor = 1]]) | Function |
These functions calculate projective_coordinates.
proj indicates which projection is to be performed.
If it is Projections::PERSP, then f indicates which
Focus is to be used (in the first version), or the global variable
default_focus is used (in the second). If
Projections::PARALLEL_X_Y, Projections::PARALLEL_X_Z, or
Projections::PARALLEL_Z_Y is used, f is ignored, since
these projections don't use a Focus. Currently, no other
projections are defined. The x and y coordinates in
projective_coordinates are multiplied by factor with the default
being 1.
|
Mathematically speaking, vectors and points are not the same. However,
they can both be represented as triples of real numbers (in a
three-dimensional Cartesian space). It is sometimes convenient to treat
points as though they were vectors, and vice versa. In particular, it
is convenient to use the same data type, namely class Point, to
represent both points and vectors in 3DLDF.
| real dot_product (Point p) | const function |
Returns the dot or scalar product of *this and p.
If P and Q are P \dot Q = x_P * x_Q + y_P * y_Q + z_P * z_Q = |P||Q| * cos(\theta)
where |P|
and |Q| are the magnitudes of
P and Q, respectively, and \theta
is the angle between P and Q.
Since \theta = arccos(P \dot Q / |P||Q|),
the dot product can be used for finding the angle between two vectors.
Point P(1, -1, -1);
Point Q(3, 2, 5);
cout << P.angle(Q);
-| 112.002
cout << P.dot_product(Q);
-| -4
real P_Q_angle = (180.0 / PI)
* acos(P.dot_product(Q)
/ (P.magnitude() * Q.magnitude()));
cout << P_Q_angle;
-| 112.002
If the angle \theta between two vectors P and Q is
90 degrees
, then
\cos(\theta) is 0, so
P \dot Q
will also be 0. Therefore,
Point P(2);
Point Q(P);
Point Q0(P0);
Q0 *= Q.rotate(0, 0, 90);
P *= Q.rotate(0, 45, 45);
P *= Q.rotate(45);
cout << P.angle(Q);
-| 90
cout << P.dot_product(Q);
-| 0
|
| Point cross_product (Point p) | const function |
Returns the cross or vector product of *this and p.
If P and Q are P * Q = ((y_P * z_Q - z_P * y_Q), (z_P * x_Q - x_P * z_Q),
(x_P * y_Q - y_P * x_Q)) = |P||Q| * sin(\theta) * n,
where |P| and |Q| are the magnitudes of
P and Q, respectively,
\theta is the angle between P and Q, and n
is a unit vector
perpendicular to both P and Q in the direction of a
right-hand screw from P towards Q. Therefore,
Point P(2, 2, 2);
Point Q(-2, 2, 2);
Point n = P.cross_product(Q);
n.show("n:");
-| n: (0, -8, 8)
real theta = (PI / 180.0) * P.angle(Q);
cout << theta;
-| 1.23096
real n_mag = P.magnitude() * Q.magnitude() * sin(theta);
cout << n_mag;
-| 11.3137
n /= n_mag;
cout << n.magnitude();
-| 1
If \theta = 0 degrees or 180 degrees, \sin(\theta) will be 0, and P * Q will be (0, 0, 0). The cross product thus provides a test for parallel vectors. Point P(1, 2, 1);
Point Q(P);
Point R;
R *= Q.shift(-3, -1, 1);
Point s(Q - R);
Point n = P.cross_product(s);
n.show("n:");
-| n: (0, 0, 0)
|
| real magnitude (void) | const function |
Returns the magnitude of the Point. This is its distance from
origin and is equal to
sqrt(x^2 + y^2 + z^2).
Point P(13, 15.7, 22);
cout << P.magnitude();
-| 29.9915
|
| real angle (Point p) | const function |
Returns the angle in degrees between two Points.
Point P(3.75, -1.25, 6.25);
Point Q(-5, 2.5, 6.25);
real angle = P.angle(Q);
cout << angle;
-| 73.9084
Point n = origin.get_normal(P, Q);
n.show("n:");
-| n: (0.393377, 0.91788, -0.0524503)
|
| Point unit_vector (const bool assign, [const bool silent = false]) | Function |
| Point unit_vector (void) | const function |
These functions return a Point with the x, y, and z-coordinates
of world_coordinates divided by the magnitude of the Point.
The magnitude of the resulting Point is thus 1. The first
version assigns the result to *this and should only ever be
called with assign = true. Calling it with the
argument false is equivalent to calling the const version,
with no assignment. If unit_vector() is called with assign
and silent both false, it issues a warning message is
issued and the const version is called. If silent is
true, the message is suppressed.
Point P(21, 45.677, 91);
Point Q = P.unit_vector();
Q.show("Q:");
-| Q: (0.201994, 0.439357, 0.875308)
P.rotate(30, 25, 10);
P.show("P:");
P: (-19.3213, 82.9627, 59.6009)
cout << P.magnitude();
-| 103.963
P.unit_vector(true);
P.show("P:");
-| P: (-0.185847, 0.797999, 0.573287)
cout << P.magnitude();
-| 1
|
| Line get_line (const Point& p) | const function |
Returns the Line l corresponding to the line from *this to
p. l.position will be *this, and
l.direction will be p - *this.
See Line Reference.
|
| real slope (Point p, [char m = 'x', [char n = 'y']]) | const function |
Returns a real number representing the slope of the trace
of the line defined by
*this and p on the plane indicated by the arguments m
and n.
Point p0(3, 4, 5);
Point p1(2, 7, 12);
real r = p0.slope(p1, 'x', 'y');
=> r == -3
r = p0.slope(p1, 'x', 'z');
=> r == -7
r = p0.slope(p1, 'z', 'y');
=> r == 0.428571
|
| bool_real is_on_segment (Point p0, Point p1) | Function |
| bool_real is_on_segment (const Point& p0, const Point& p1) | const function |
These functions return a bool_real, where the bool part is
true, if
the Point lies on the line segment between p0 and p1,
otherwise false. If the Point lies on the line segment, the
real part is a value
r such that
0 <= r <= 1
indicating how far the Point is along the way from
p0 to p1. For example, if the Point is half of the way
from p0 to p1, r will be .5. If the Point
does not lie on the line
segment, but on the line passing through p0 and p1,
r will be <0 or >1.
If the Point p0(-1, -2, 1);
Point p1(3, 2, 5);
Point p2(p0.mediate(p1, .75));
Point p3(p0.mediate(p1, 1.5));
Point p4(p2);
p4.shift(-2, 1, -1);
bool_real br = p2.is_on_segment(p0, p1);
cout << br.first;
-| 1
cout << br.second;
-| 0.75
bool_real br = p3.is_on_segment(p0, p1);
cout << br.first;
-| 0
cout << br.second;
-| 1.5
bool_real br = p4.is_on_segment(p0, p1);
cout << br.first;
-| 0
cout << br.second;
-| 3.40282e+38
cout << (br.second == INVALID_REAL)
-| 1
|
| bool_real is_on_line (const Point& p0, const Point& p1) | const function |
Returns a bool_real where the bool part is true, if
the Point lies on the line passing through p0 and p1,
otherwise false. If the Point lies on the line, the
real part is a value r indicating how how far the Point
is along the way from p0 to p1, otherwise
INVALID_REAL. The following
values of r are possible for a call to P.is_on_line(A, B),
where the Point P lies on the line
AB:
P == A ---> r== 0.
P == B ---> r== 1.
P lies on the opposite side of A from B ---> r < 0.
P lies between A and B ---> 0 < r < 1.
P lies on the opposite side of A from B ---> r > 1
Point A(-1, -2);
Point B(2, 3);
Point C(B.mediate(A, 1.25));
bool_real br = C.is_on_line(A, B);
Point D(A.mediate(B));
br = D.is_on_line(A, B);
Point E(A.mediate(B, 1.25));
br = E.is_on_line(A, B);
Point F(D);
F.shift(-1, 1);
br = F.is_on_line(A, B);
|
| Point mediate (Point p, [const real r = .5]) | const function |
Returns a Point r of the way from *this to p.
Point p0(-1, 0, -1);
Point p1(10, 0, 10);
Point p2(5, 5, 5);
Point p3 = p0.mediate(p1, 1.5);
p3.show("p3:");
-| p3: (15.5, 0, 15.5)
Point p4 = p0.mediate(p2, 1/3.0);
p4.show("p4:");
-| p4: (1, 1.66667, 1)
|
| bool_point intersection_point (Point p0, Point p1, Point q0, Point q1) | Static function |
| bool_point intersection_point (Point p0, Point p1, Point q0, Point q1, const bool trace) | Static function |
|
These functions find the intersection point, if any, of the lines determined by
p0 and p1 on the one hand, and q0 and q1 on the other.
Let The two versions use different methods of finding the intersection
point. The first uses a vector calculation, the second looks for the
intersections of the traces of the lines on the major planes. If the
trace argument is used, the second version will be called, whether
trace is Point A(-1, -1);
Point B(1, 1);
Point C(-1, 1);
Point D(1, -1);
bool_point bp = Point::intersection_point(A, B, C, D);
bp.pt.dotlabel("$i$");
cout << "bp.b == " << bp.b << endl << flush;
-| bp.b == 1
Point A(.5, .5);
Point B(1.5, 1.5);
Point C(-1, 1);
Point D(1, -1);
bool_point bp = Point::intersection_point(A, B, C, D, true);
bp.pt.dotlabel("$i$");
cout << "bp.b == " << bp.b << endl << flush;
-| bp.b == 0
|
There are two versions for each of the drawing functions. The second
one has the Picture argument picture at the beginning of the
argument list, rather than at the end. This is convenient when passing
a picture argument. Where picture is optional, the default
is always current_picture.
void drawdot ([const Color& ddrawdot_color = *Colors::default_color, [const string ppen = "", [Picture& picture = current_picture]]])
|
const function |
void drawdot ([Picture& picture = current_picture, [const Color& ddrawdot_color = *Colors::default_color, [const string ppen = "", ]]])
|
const function |
Draws a dot on picture. If ppen is specified, a "pen
expression" is included in the drawdot command written to
out_stream. Otherwise, MetaPost's currentpen is used.
If ddrawdot_color is specified, the dot will be drawn using that
Color. Otherwise, the Color currently pointed to by the pointer
Colors::default_color will be used. This will normally be
Colors::black. See Color Reference, for more information
about Colors and the namespace Colors.
Please note that the "dot" will always be parallel to the plane of projection. Even where it appears to be a surface, as in [next figure] , it is never put into perspective, but will always have the same size and shape. Point P(1, 1);
P.drawdot(gray, "pensquare scaled 1cm");
|
void undrawdot ([string pen = "", [Picture& picture = current_picture]])
|
Function |
void undrawdot ([Picture& picture = current_picture, [string pen = ""]])
|
Function |
Undraws a dot on picture. If ppen is specified, a "pen
expression" is included in the undrawdot command written to
out_stream. Otherwise, MetaPost's currentpen is used.
Point P(1, 1);
P.drawdot(gray, "pensquare scaled 1cm");
P.undrawdot("pencircle scaled .5cm");
|
void draw (const Point& p, [const Color& ddraw_color = *Colors::default_color, [string ddashed = "", [string ppen = "", [Picture& picture = current_picture, [bool aarrow = false]]]]])
|
Function |
void draw (Picture& picture = current_picture, const Point& p, [const Color& ddraw_color = *Colors::default_color, [string ddashed = "", [string ppen = "", [bool aarrow = false]]]])
|
Function |
Draws a line from *this to p.
Returns the Path *this -- p1.
See Path Reference; Drawing and Filling,
for more information.
Point P(-1, -1, -1);
Point Q(2, 3, 5);
P.draw(Q, Colors::gray, "", "pensquare scaled .5cm");
|
void undraw (const Point& p, [string ddashed = "", [string ppen = "", [Picture& picture = current_picture]]])
|
Function |
| void undraw (Picture& picture, const Point& p, [string ddashed = "", [string ppen = ""]]) | Function |
Undraws a line from *this to p.
Returns the Path *this -- p1.
See Path Reference; Drawing and Filling,
for more information.
Point P(-1, -1, -1);
Point Q(2, 3, 5);
P.draw(Q, Colors::gray, "", "pensquare scaled .5cm");
P.undraw(Q, "evenly scaled 6", "pencircle scaled .3cm");
|
| Path draw_help (const Point& p, [const Color& ddraw_color = *Colors::help_color, [string ddashed = "", [string ppen = "", [Picture& picture = current_picture]]]]) | Function |
| Path draw_help (Picture& picture, const Point& p, [const Color& ddraw_color = *Colors::help_color, [string ddashed = "", [string ppen = ""]]]) | Function |
Draws a "help line" from *this to p, but only if the
static Path data member do_help_lines is true.
See Path Reference; Data Members.
"Help lines" are lines that are used when constructing a drawing, but that should not be printed in the final version. |
Path drawarrow (const Point& p, [const Color& ddraw_color = *Colors::default_color, [string ddashed = "", [string ppen = "", [Picture& picture = current_picture]]]])
|
Function |
Path drawarrow (Picture& picture, const Point& p, [const Color& ddraw_color = *Colors::default_color, [string ddashed = "", [string ppen = ""]]])
|
Function |
Draws an arrow from *this to p and returns
the Path *this -- p.
The second version is convenient for passing a Picture argument
without having to specify all of the other arguments.
Point P(-3, -2, 1);
Point Q(3, 3, 5);
P.drawarrow(Q);
|
Labels make it possible to include TeX text within a drawing.
Labels are implemented by means of class Label.
The functions label() and dotlabel(), described in this
section, create objects of type Label, and add them to the
Picture, which was passed to them as an argument
(current_picture, by default).
See Label Reference, for more information.
void label (const string text_str, [const string position_str = "top", [const bool dot = false, [Picture& picture = current_picture]]])
|
const function |
void label (const short text_short, [const string position_str = "top", [const bool dot = false, [Picture& picture = current_picture]]])
|
const function |
These functions cause a Point to be labelled in the drawing.
The first argument is the text of the label. It can either be a
string, in the first version, or a short, in the second.
It will often be the name of the Point in the C++
code, for
example, "p0".
It is not possible to automate this kind of
labelling, because it is not possible to access the names of variables
through the variables themselves in C++
.
text_str is always placed between
" Point p0(2, 3);
p0.label("$p_0$");
If backslashes are needed in the text of the label, then
text_str must contain double backslashes, so that single
backslashes will be written to Point P;
Point Q(2, 2);
Point R(P.mediate(Q));
R.label("$\\overrightarrow{PQ}$", "ulft");
The position argument indicates where the text of the label should
be located relative to the The dot argument is used to determine whether the label should be
dotted or not. The default is |
void dotlabel ([const string text_str, [const string position_str = "top", [Picture& picture = current_picture]]])
|
const function |
| void dotlabel (const short text_short, [const string position_str = "top", [Picture& picture = current_picture]]) | const function |
These functions are like label() except that they always produces a
dot.
Point p0(2, 3);
p0.dotlabel("$p_0$");
|
| void show ([string text = "", [char coords = 'w', [const bool do_persp = true, [const bool do_apply = true, [Focus* f = 0, [const unsigned short proj = Projections::persp, [const real factor = 1]]]]]]]) | const function |
Prints text followed by the values of a set of coordinates to
standard output (stdout). The other arguments are similar to
those used in the functions described in Returning Coordinates.
Point P(1, 3, 5);
P.rotate(15, 67, 98);
P.show("P:");
-| P: (-3.68621, -3.89112, 2.50421)
|
| void show_transform ([string text = ""]) | Function |
Prints text to standard output (stdout),
or "transform:", if text is the empty string (the
default), and then
calls transform.show().
Point A(-1, 1, 1);
Point B(13, 12, 6);
Point Q(31, 17.31, 6);
Q.rotate(A, B, 32);
Q.show_transform("Q.transform:");
-| Q.transform:
Transform:
0.935 0.212 -0.284 0
-0.0749 0.902 0.426 0
0.346 -0.377 0.859 0
-0.336 0.687 -0.569 1
|
| ostream& operator<< (ostream& o, Point& p) | Non-member function |
Used in Path::output() for writing
the x and y values of the projective_coordinates of Points to
out_stream. See Path Reference; Outputting.
This is a low-level function that ordinary users should never have to
invoke directly.
|
| void output (void) | Function |
Writes the MetaPost code for drawing or undrawing a Point to
out_stream. Called by Picture::output(), when a
Shape on the Picture is a Point.
See Picture Reference; Outputting.
|
| void suppress_output (void) | Virtual function |
Sets do_output to false, which causes a Point
not to be output. This function is called in
Picture::output(), when a Point cannot be projected.
See Picture Reference; Outputting.
|
| virtual void unsuppress_output (void) | Virtual function |
Resets do_output to true, so that a Point can
potentially be output, if Picture::output() is called again for
the Picture the Point is on.
This function is called in
Picture::output().
See Picture Reference; Outputting.
|
vector<shape*> extract (const Focus& f, const unsigned short proj, real factor)
|
Function |
Attempts to project the Point
using the arguments passed to Picture::output(), which calls this
function. If extract() succeeds,
it returns a vector<shape*> containing only the Point.
Otherwise, it returns an empty vector<shape*>.
|
| bool set_extremes (void) | Virtual function |
Sets "extreme" values
for x, y, and z in projective_coordinates. This
is, of course, trivial for
Points, because they only have one x, y and z-coordinate.
So the maxima and minima for each coordinate are always the same.
|
valarray <real> get_extremes (void)
|
Virtual inline const function |
Returns projective_extremes.
|
| real get_minimum_z (void) | Virtual const function |
| real get_maximum_z (void) | Virtual const function |
| real get_mean_z (void) | Virtual const function |
These functions return the minimum, maximum, and mean z-value of the
Point.
get_minimum_z() returns projective_extremes[4],
get_maximum_z() returns projective_extremes[5], and
get_mean_z() returns
(projective_extremes[4] + projective_extremes[5]) / 2.
However, since a Point has only one z-coordinate
(from world_coordinates), these values will all be the same.
These functions are pure virtual functions in |
Class Focus is defined in points.web.
Focuses are used when creating a perspective projection.
They represent the center of projection and can be thought of like a
camera viewing the scene.
| Point position | Private variable |
The location of the Focus in the world coordinate system.
|
| Point direction | Private variable |
The direction of view from position into the scene.
|
| Point up | Private variable |
| The direction that will be at the top of the projected drawing. |
| real distance | Private variable |
The distance of the Focus from the plane of projection.
|
| real angle | Private variable |
Used for determining the up direction.
|
| char axis | Private variable |
The main axis onto which the Focus is transformed in order to
perform the perspective projection, z by default.
It will normally not matter which axis is used, but it might be advantageous to use a particular axis in some special situations. |
| Transform transform | Private variable |
The Transform, which will be applied to the Shapes on the
Picture, when the latter is output. The effect of this is
equivalent to transforming the Focus, so that it lies on a major
axis.
Focus f(5, 5, -10, 2, 4, 10, 10, 180);
=>
|
| Transform persp | Private variable |
The Transform representing the perspective transformation for a
particular Focus.
Let d stand for distance, then
|
| Focus default_focus | Variable |
Effectively, the default Focus in Picture::output().
See Picture Reference; Outputting; Functions.
It's not really the default, but the version of
output() that doesn't take a
Focus argument calls another version
that does take one, passing default_focus to the
latter as its Focus argument.
It's necessary to do this in such a roundabout way,
because The declaration |
| void Focus (void) | Default constructor |
Creates an empty Focus
|
| void Focus (const real pos_x, const real pos_y, const real pos_z, const real dir_x, const real dir_y, const real dir_z, const real dist, [const real ang = 0, [char ax = 'z']]) | Constructor |
Constructs a Focus using the first three real arguments as
the x, y, and z-coordinates of position, and the fourth through the
sixth argument as the x, y, and z-coordinates of direction. dist
specifies the distance of the Focus
from the plane of projection, ang the angle of rotation, which affects which
direction is considered to be "up", and ax the major axis to
which the Focus is aligned.
|
| void set (const real pos_x, const real pos_y, const real pos_z, const real dir_x, const real dir_y, const real dir_z, const real dist, [const real ang = 0, [char ax = 'z']]) | Setting function |
Resets an existing Focus. Corresponds to the constructor above.
|
| void Focus (const Point& pos, const Point& dir, const real dist, [const real ang = 0, [char ax = 'z']]) | Constructor |
Constructs a Focus using Point arguments for
position and direction. Otherwise, the arguments of this constructor
correspond to those of the one above.
|
| void set (const Point& pos, const Point& dir, const real dist, [const real ang = 0, [char ax = 'z']]) | Setting function |
Resets an existing Focus. Corresponds to the constructor above.
|
| const Focus& operator= (const Focus& f) | Assignment operator |
Sets the Focus to f.
|
| void reset_angle (const real ang) | Function |
Resets the value of angle and recalculates the Transforms
transform and persp.
|
| const Point& get_position (void) | Inline const function |
Returns position.
|
| const Point& get_direction (void) | Inline const function |
Returns direction.
|
| const real& get_distance (void) | Inline const function |
Returns distance.
|
| const Point& get_up (void) | Inline const function |
Returns up.
|
| const Transform& get_transform (void) | Inline const function |
Returns transform.
|
| const real& get_transform_element (const unsigned int row, const unsigned int column) | Inline const function |
Returns an element of transform, given two unsigned ints for the row
and the column.
|
| const Transform& get_persp (void) | Inline const function |
Returns persp.
|
| const real& get_persp_element (const unsigned int row, const unsigned int column) | Inline const function |
Returns an element of persp, given two unsigned ints for the row
and the column.
|
| void show ([const string text_str = "Focus:", [const bool show_transforms = false]]) | const function |
Prints text_str to standard output (stdout), then calls
Point::show() on position, direction, and
up. Then the values of distance, axis, and
angle are printed to stdout. If show_transforms is
true, transform and persp are shown as well.
|
The struct Line is defined in lines.web.
Lines are not Shapes. They are used for
performing vector operations. A Line is defined by a
Point representing a position vector and a Point
representing a direction vector.
See also the descriptions of Point::get_line() in
Points and Lines, and
Path::get_line() in
Path Reference; Querying.
| Point position | Public variable |
Represents the position vector of the Line.
|
| Point direction | Public variable |
Represents the direction vector of the Line.
|
| const Line INVALID_LINE | Constant |
position and direction are both INVALID_POINT.
|
void Line (const Point& pos = origin, const Point& dir = origin)
|
Default constructor |
Creates a Line, setting position to pos, and
direction to dir. If this function is called with no
arguments, it creates a Line at the origin with no
direction.
Point p(2, 1, 2);
Point d(-3, 3, 3.5);
Line L0(p, d);
Line L1 = p.get_line(d);
|
| void Line (const Line& l) | Copy constructor |
Creates a Line, making it a copy of l.
|
| void operator= (const Line& l) | Assignment operator |
Sets *this to l.
|
| Path get_path (void) | const function |
Returns a linear Path with two Points
on the Line. The first Point will be position, and
the second will be position + direction.
|
| void show ([string text = ""]) | Function |
If text is not the empty string (the default), it is
printed on a line of its own to standard output. Otherwise, Line:
is printed. Following this, Point::show() is called on
position and direction.
Point p(1, -2, 3);
Point d(-12.3, 21, 36.002);
Line L0(p, d);
L0.show("L0:");
-| L0:
position: (1, -2, 3)
direction: (-12.3, 21, 36.002)
Line L1 = p.get_line(d);
L1.show("L1:");
-| L1:
position: (1, -2, 3)
direction: (-13.3, 23, 33.002)
Path q = L1.get_path();
q.show("q:");
-| q:
fill_draw_value == 0
(1, -2, 3) -- (-12.3, 21, 36.002);
|
The struct Plane is defined in planes.web.
Planes are not Shapes. They are used for
performing vector operations. A Plane is defined by a
Point representing a point on the plane, a Point
representing the normal to the plane, and the distance of the plane from
the origin.
The most common use of Planes is to represent the
plane in which an existing plane figure lies. Therefore, they
most likely to be created by using Path::get_plane().
See Path Reference; Querying. However, class
Plane does have constructors for creating Planes directly, if
desired.
See Planes Reference; Constructors.
Because the main purpose of Plane is to
provide information about Shapes, its data members are all
public.
| Point point | Public variable |
| Represents a point on the plane. |
| Point normal | Public variable |
| Represents the normal to the plane. |
| real distance | Public variable |
| The distance of the plane from the origin. |
| const Plane INVALID_PLANE | Constant |
A Plane with point == normal, and
distance == INVALID_REAL.
|
| void Plane (void) | Default constructor |
Creates a degenerate Plane with
point == normal == origin, and
distance == 0.
|
| void Plane (const Plane& p) | Copy constructor |
Creates a new Plane, making it a copy of p.
|
| void Plane (const Point& p, const Point& n) | Constructor |
If p is not equal to n, this constructor creates a
Plane and sets point to p. normal
is set to n, and made a unit vector.
distance is calculated according to the following formula:
Let n stand for normal, p for point, and d for
distance:
d = -p \dot n.
If d = 0, origin
lies in the Plane. If d > 0, origin lies on the side of the
Plane that normal points to, considered to be "outside".
If d<0, origin lies on the side of the
Plane that normal does not point to, considered to be
"inside".
However, if p == n, Point P(1, 1, 1);
Point N(0, 1);
N.rotate(-35, 30, 20);
N.show("N:");
-| N: (-0.549659, 0.671664, 0.496732)
Plane q(P, N);
cout << q.distance;
-| -0.618736
|
| const Plane& operator= (const Plane& p) | Assignment operator |
Sets point to p.point, normal to
p.normal, and distance to p.distance.
The return value is p, so that invocations of this function can be
chained.
Point pt(2, 2.3, 6);
Point norm(-1, 12, -36);
Plane A(pt, norm);
Plane B;
Plane C;
B = C = A;
A.show("A:");
-| A:
normal: (-0.0263432, 0.316118, -0.948354)
point: (2, 2.3, 6)
distance == 5.01574
cout << (A == B && A == C && B == C);
-| 1
|
| bool operator== (const Plane& p) | const operator |
Equality operator. Compares *this and p, and returns
true, if point == p.point,
normal == p.normal, and
distance == p.distance,
otherwise false.
|
| bool operator!= (const Plane& p) | const operator |
Inequality operator. Compares *this and p and returns
true, if point !=
p.point, or
normal !=
p.normal, or
distance !=
p.distance.
Otherwise, it returns false.
|
| real_short get_distance (const Point& p) | const function |
| real_short get_distance (void) | const function |
The version of this function taking a Point argument returns
a real_short r, whose real part
(r.first) represents
the distance of p from the Plane. This value is always
positive. r.second can take on three values:
The version taking no argument returns
the absolute of the data member It would have been possible to use Point N(0, 1);
N.rotate(-10, 20, 20);
Point P(1, 1, 1);
Plane q(P, N);
Point A(4, -2, 4);
Point B(-1, 3, 2);
Point C = q.intersection_point(A, B).pt;
real_short bp;
bp = q.get_distance();
cout << bp.first;
-| 0.675646
cout << bp.second
-| -1
bp = q.get_distance(A)
cout << bp.first;
-| 3.40368
cout << bp.second;
-| -1
bp = q.get_distance(B)
cout << bp.first;
-| 2.75865
cout << bp.second;
-| 1
bp = q.get_distance(C)
cout << bp.first;
-| 0
cout << bp.second;
-| 0
|
| bool_point intersection_point (const Point& p0, const Point& p1) | const function |
| bool_point intersection_point (const Path& p) | const function |
These functions find the intersection point of the Plane and a
line. In the first version, the
line is defined by the two Point arguments. In the second
version, the Path p must be linear, i.e.,
p.is_linear() must be true.
Both versions of Point center(2, 2, 3.5);
Reg_Polygon h(center, 6, 4, 80, 30, 10);
Plane q = h.get_plane();
Point P0 = center.mediate(h.get_point(2));
P0.shift(5 * (N - center));
Point P1(P0);
P1.rotate(h.get_point(1), h.get_point(4));
P1 = 3 * (P1 - P0);
P1.shift(P0);
P1.shift(3, -.5, -2);
bool_point bp = q.intersection_point(P0, P1);
Point i_P = bp.pt;
Point P4 = h.get_point(3).mediate(h.get_point(0), .75);
P4.shift(N - center);
Point P5(P4);
P5.rotate(h.get_point(3), h.get_point(0));
P4.shift(-1, 2);
Path theta(P4, P5);
bp = q.intersection_point(theta);
Point i_theta = bp.pt;
draw_axes();
|
| Line intersection_line (const Plane& p) | const function |
Returns a Line l. representing the line of intersection of two
Planes. See Line Reference.
In [next figure]
, Rectangle r0(origin, 5, 5, 10, 15, 6);
Rectangle r1(origin, 5, 5, 90, 50, 10);
r1 *= r0.rotate(30, 30, 30);
r1 *= r0.shift(1, -1, 3);
Plane q0 = r0.get_plane();
Plane q1 = r1.get_plane();
Line l = q0.intersection_line(q1);
l.show("l:");
-| l:
position: (0, 11.2193, 20.0759)
direction: (0.0466595, -0.570146, -0.796753)
Point P0(l.direction);
P0.shift(l.position);
P0.show("P0:");
-| P0: (0.0466595, 10.6491, 19.2791)
Point P1(-l.direction);
P1.shift(l.position);
Point P2(P0 - P1);
P2 *= 12.5;
P2.shift(P0);
cout << P2.is_on_plane(q0);
-| 1
cout << P2.is_on_plane(q1);
-| 1
Point P3(P0 - P1);
P3 *= 7;
P3.shift(P0);
cout << P3.is_on_plane(q0);
-| 1
cout << P3.is_on_plane(q1);
-| 1
|
| void show ([string text = ""]) | const function |
Prints information about the Plane to standard output.
If text is not the empty string, it is printed to the
standard output. Otherwise,
Plane: is printed.
Following this,
if the Plane is equal to INVALID_PLANE
(see Planes Reference; Global Constants),
a message to this effect is printed to standard output.
Otherwise, normal and
point are shown using Point::show()
(see Point Reference; Showing). Finally,
distance is printed.
Point A(1, 3, 2.5);
Rectangle r0(A, 5, 5, 10, 15, 6);
Plane p = r0.get_plane();
-| p:
normal: (-0.0582432, 0.984111, -0.167731)
point: (-0.722481, 2.38245, -0.525176)
distance == -2.47476
|
Class Path is defined in paths.web.
It is derived from Shape using protected derivation.
| bool line_switch | Protected variable |
true if the Path was created using the constructor
Path(const Point& p0, const Point& p1), directly or indirectly.
See Path Reference; Constructors and Setting Functions.
Point p0;
Point p1(1, 1);
Point p2(2, 3);
Path q0(p0, p1);
cout << q0.get_line_switch();
-| 1
Path q1;
q1 = q0;
cout << q1.get_line_switch();
-| 1
Path q2 = p0.draw(p1);
cout << q2.get_line_switch();
-| 1
Path q3("..", false, &p1, &p2, &p0, 0);
cout << q3.get_line_switch();
-| 0
Some |
| bool cycle_switch | Protected variable |
true if the Path is cyclical, otherwise
false.
|
| bool on_free_store | Protected variable |
true if the Path was dynamically allocated on the free
store. Otherwise false. Set to true only in
create_new<Path>(), which should be the only way Paths are
ever dynamically allocated.
See Path Reference; Constructors and Setting Functions.
|
| bool do_output | Protected variable |
Used in Picture::output(). Set to false if the Path
isn't projectable using the arguments passed to
Picture::output().
See Picture Reference; Outputting.
|
| signed short fill_draw_value | Protected variable |
Set in the drawing and filling functions, and
used in Path::output(), to determine what MetaPost code to write
to out_stream.
See Path Reference; Drawing and Filling,
and Path Reference; Outputting.
|
| const Color* draw_color | Protected variable |
Pointer to the Color used if the Path is drawn.
|
| const Color* fill_color | Protected variable |
Pointer to the Color used if the Path is filled.
|
| string dashed | Protected variable |
String written to out_stream for the "dash pattern" in a
MetaPost draw or undraw command. If and only if
dashed is not the empty string, "dashed
<dash pattern>" is written to out_stream.
Dash patterns have no meaning inside 3DLDF; |
| string pen | Protected variable |
String written to out_stream for the pen to be used in a
MetaPost draw, undraw, filldraw, or
unfilldraw command. If and only if pen is not the
empty string, "withpen <...>" is written to
out_stream.
Pens have no meaning inside 3DLDF; |
| bool arrow | Protected variable |
Indicates whether an arrow should be drawn when outputting a
Path. Set to true on a Path created on the free
store and put onto a Picture by drawarrow().
|
| valarray<real> projective_extremes | Protected variable |
Contains the maxima and minima of the x, y, and z-coordinates of the
projections of Points on a Path using a particular
Focus. Set in set_extremes() and used in
Picture::output() for surface hiding.
|
| vector<Point*> points | Protected variable |
Pointers to the Points on the Path.
|
| vector<string> connectors | Protected variable |
The connectors between the Points on the Path. Connectors
are simply strings in 3DLDF, they are written unchanged to
out_stream.
|
| const Color* help_color | Public static variable |
Pointer to a const Color, which becomes the default for
draw_help().
See Path Reference; Drawing and Filling.
Please note that |
| string help_dash_pattern | Public static variable |
The default dash pattern for draw_help().
|
| bool do_help_lines | Public static variable |
true if help lines should be output, otherwise false.
If false, a call to draw_help() does not cause a copy of
the Path to be created and put onto a Picture.
See Path Reference; Drawing and Filling.
|
| void Path (void) | Default constructor |
Creates an empty Path with no
Points and no connectors.
|
| void Path (const Point& p0, const Point& p1) | Constructor |
Creates a line (more precisely, a line segment) between p0
and p1. The single
connector between the two Points is set to "--" and the
data member line_switch (of type bool) is set to
true. There are certain operations on Paths that are only
applicable to lines, so it's necessary to store the information that a
Path is a line.36
Point A(-2, -2.5, -1);
Point B(3, 2, 2.5)
Path p(A, B);
p.show("p:");
-| p:
(-2, -2.5, -1) -- (3, 2, 2.5);
|
| void set (const Point& p0, const Point& p1) | Setting function |
Corresponds to the constructor above.
Point P0(1, 2, 3);
Point P1(3.5, -12, 75);
Path q;
q.set(P0, P1);
q.show("q:");
-| q:
(1, 2, 3) -- (3.5, -12, 75);
|
| void Path (string connector, bo |