% D. Roegel, 25/2/1997 : first draft % 26/2/1997 % 23/4/1997 : appendix, types added % 24/4/1997 : better formatting of appendix % 25/4/1997 : two columns % 27/4/1997 : some improvements % 28/4/1997 : changes to take new syntax into account % 30/4/1997 : cleaning and additions % 1/5/1997 : ltugboat macros % addition of appendix b % all overfull hboxes removed; this needed a lot % of rephrasing! % lots of cleaning % 2/5/1997 : extension of future part % 12/5/1997 : several small improvements to take Ulrik Vieth's % comments into account. % 17/5/1997 : some corrections to take modifications in % the source code into account % 18/5/1997 : commas in the syntax have been put in \texttt % some renamings in order to get rid of the overfull % hboxes resulting from the comma changes ... % 29/5/1997 : some renamings of ``object'' into ``obj'' to % be in accordance with the code (version 0.993) % some reformatting of pieces of code with respect % to the indentation % 19/6/1997 : - the `future' part has been corrected with respect % to the general algorithm for drawing the faces % (thanks to Dominique Larchey) % - reference to the ``LaTeX Graphics Companion'' % 11/2/1998 : - description of draw_contours and contour_width % - acknowledgment of Denis Barbier and Boguslaw % Jackowski % - one_image changed to an_image at the beginning % of the paper, in order to avoid being misleading % by comparison with the real one_image macro % - a few more lines describing the parameters of % one_image % - ghostscript -> Ghostscript % - footnote added to explain why Ghostscript % has not been used to generate the excerpts % of the images. % - some lines to explain why the color type % was not used for 3d vectors % 12/2/1998 : - rewording to avoid overfull hboxes % 19/2/1998 : - minor ``english'' editing (RF) % and file sent to author for review (mb) % 04/03/1998: - EM fonts removed/ not to be used this issue % 04/08/1998: - TUB-specific inputs modified for portability to % CTAN archives (mb) %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \documentclass[nonumber,harvardcite]{ltugboat} \usepackage{mflogo} \usepackage{url} \usepackage[dvips]{graphicx} \newcommand{\AVN}{\meta{avn}} \newcommand{\LVN}{\meta{lvn}} \newcommand{\APN}{\meta{apn}} \newcommand{\LPN}{\meta{lpn}} \newcommand{\AFN}{\meta{afn}} \newcommand{\LFN}{\meta{lfn}} \newcommand{\CN}{\meta{cl}} \newcommand{\IN}{\meta{obj}} \newcommand{\VL}{\meta{vl}} \newcommand{\VSL}{\meta{vsl}} \newcommand{\HEXCOL}{\meta{hc}} \newcommand{\COL}{\meta{col}} \newcommand{\STR}{\meta{str}} \newcommand{\PAIR}{\meta{pair}} \newcommand{\NUM}{\meta{num}} \newcommand{\BOOL}{\meta{bool}} \newcommand{\tc}{\texttt{,}} \newenvironment{todo}{\begin{bfseries}}{\end{bfseries}} %% this command is already defined in ltugboat.cls and is more robust %% I think.... %\newcommand\meta[1]{$\langle$\mbox{\textit{#1}}$\rangle$} %%% the following is the ltugboat.cls definition %\DeclareRobustCommand\meta[1]{% % \ensuremath{\langle}\emph{#1}\ensuremath{\rangle}} \let\m=\meta % <-- this command is never used. %%%%%TUGboat production-specific files \vol 18, 4. % volume, issue. \issueseqno=57 % sequential issue number \issdate December 1997. % month, year of publication \setcounter{page}{274} \NoBlackBoxes \PrelimDraftfalse \widowpenalty=10000 \clubpenalty=10000 \renewcommand{\topfraction}{0.9} \renewcommand{\bottomfraction}{0.5} \renewcommand{\floatpagefraction}{0.8} \renewcommand{\textfraction}{0.1} \setcounter{bottomnumber}{2} \setcounter{totalnumber}{4} \renewcommand{\dbltopfraction}{0.9} \renewcommand{\dblfloatpagefraction}{0.8} \pretolerance=500 \tolerance=1000 \hbadness=3000 \vbadness=3000 \hyphenpenalty=400 %%%%%%%END of TUGboat production-specific files \begin{document} \sectitle{Graphics Applications} \title{Creating 3D animations with \MP} \author{Denis Roegel} \address{CRIN (Centre de Recherche en Informatique de Nancy)\\ B\^atiment LORIA\\ BP 239\\ 54506 Vand\oe uvre-l\`es-Nancy\\ FRANCE} \netaddress{roegel@loria.fr} \personalURL{http://www.loria.fr/~roegel} \maketitle \begin{abstract} \MP{} can be used to create animations. We show here an example of animation of polyhedra, introducing the \texttt{3d} package. \end{abstract} \section{Introduction} %%%RF 1998/02/19: N-forms of cite \MP{} (\citeN{hobby1992}; see also the description in \shortciteN{Goossens:LGC97}) is a drawing language very similar to \MF, but whose output is \PS. \MP{} is especially suited for geometrical and technical drawings, where a drawing can naturally be decomposed in several parts, related in some logical way. %%%RF 1998/02/19: active voice for passive Knuth is using \MP{} for the revisions of and additions to \emph{The Art Of Computer Programming}~\cite{knuth1997}, and it is or will be a component of every standard \TeX{} distribution. Unfortunately, \MP{} is still quite bare and the user is only offered %%%RF 1998/02/19: revamped a bit the raw power\Dash a little bit like the \TeX{} user who only has plain \TeX{} at his/her disposal. The lack of libraries is certainly due to the infancy of \MP{} (which came in the public domain at the beginning of 1995) and thus to the small number of its users. In this paper, we present a way to produce animations using \MP. The technique is quite general and we illustrate it through the \texttt{3d} package. \section{Animations} The World Wide Web has accustomed us to various animations, especially \texttt{java} animations. Common components of web pages are animated GIF images. Producing animations in \MP{} is actually quite easy. A number of $n$ images will be computed and their sequence produces the animation. The animation will be similar to a movie, with no interaction. More precisely, if \verb|an_image(|$i$\verb|)| produces a picture parameterized by $i$, it suffices to wrap this macro between \verb|beginfig| and \verb|endfig|: \begin{verbatim} def one_image_out(expr i)= beginfig(
); an_image(i); endfig; enddef; \end{verbatim} \noindent and to loop over \verb|one_image_out|: \begin{verbatim} for j:=1 upto 100:one_image_out(j);endfor; \end{verbatim} Assuming that \verb|
| is equal to the parameter of \verb|an_image|, the compilation of this program will produce a hundred files with extensions \verb|.1|, \verb|.2|, \ldots, \verb|.100|. All these files are \PS{} files and all we need to do is to find a way to collate them in one piece. How to do this depends on the operating system. On UNIX for instance, one can use \texttt{Ghostscript} to transform a \PS{} file into \texttt{ppm} and then transform each \texttt{ppm} file into GIF with \texttt{ppmtogif}. These programs are part of the \texttt{NETPBM} package~\cite{netpbm}. Finally, a program such as \texttt{gifmerge}~\cite{gifmerge} creates an animated GIF file (GIF89A) out of the hundred individual simple GIFs. However, various details must be taken care of. For instance, %%%RF 1998/02/19: reorganised around `grabbed'->`needed' only a part of \texttt{Ghostscript}'s output is needed and selection can be made with \texttt{pnmcut}. %(which is also part of \texttt{NETPBM}). The whole process of creating an animation out of \MP's outputs can be summed up in a shell script, similar to the one in figure~\ref{animation-script}. As we will see, this script (including the arguments of \texttt{awk} and \texttt{pnmcut}) can be generated automatically by \MP{} itself. %%fig 1 \begin{figure*} \begin{verbatim} #! /bin/sh /bin/rm -f animpoly.log for i in `ls animpoly.*| grep 'animpoly.[0-9]'`;do echo $i echo '==============' # shift each picture so that it lies in the page: awk < $i '{print} /^%%Page: /{print "172 153 translate\n"}' > $i.ps # produce ppm format: gs -sDEVICE=ppmraw -sPAPERSIZE=a4 -dNOPAUSE -r36 -sOutputFile=$i.ppm -q -- $i.ps /bin/rm -f $i.ps # produce gif: ppmquant 32 $i.ppm | pnmcut 15 99 141 307 | ppmtogif > `expr $i.ppm : '\(.*\)ppm'`gif /bin/rm -f $i.ppm done /bin/rm -f animpoly.gif # merge the gif files: gifmerge -10 -l1000 animpoly.*.gif > animpoly.gif /bin/rm -f animpoly.*.gif \end{verbatim} \caption{Script created by \MP{} (with some additional comments)} \label{animation-script} \end{figure*} \section{Objects in space} \subsection{Introduction} The author applied this idea to the animation of objects in space. The macros in the \texttt{3d.mp} package\footnote{On CTAN, under \texttt{graphics/metapost/macros/3d}. The code is documented with \texttt{MFT}~\cite{knuth1989} and illustrated with \MP. This paper describes version 1.0 of the macros.} provide a basis for the representation of three-dimensional objects. The basic components of the objects are the points or the vectors. Both are stored as triplets. More precisely, we have three arrays\footnote{\MP{} has a few simple types such as \texttt{numeric}, \texttt{boolean}, \texttt{string}, \texttt{path}, \ldots. It also has pairs (\texttt{pair}) and triples (\texttt{color}). We might have cheated and stored points as colors, but instead, we found it interesting to illustrate a construction equivalent to \textsc{Pascal}'s records or C's structures. In \MP, instead of having a list or an array of structures, we use several lists or arrays, so that a record is a cross-section over several arrays.} of type \verb|numeric|: \begin{verbatim} numeric vect[]x,vect[]y,vect[]z; \end{verbatim} Vector $i$'s components are \verb|vect[|$i$\verb|]x|, \verb|vect[|$i$\verb|]y| and \verb|vect[|$i$\verb|]z|. It is then straightforward to define the usual operations on vectors using this convention. For instance, vector addition is defined as: \begin{verbatim} def vect_sum(expr k,i,j)= vect[k]x:=vect[i]x+vect[j]x; vect[k]y:=vect[i]y+vect[j]y; vect[k]z:=vect[i]z+vect[j]z; enddef; \end{verbatim} Often, we need some scratch vectors or vectors local to a macro. A simple vector allocation mechanism solves the problem: we use a stack of vectors and we reserve and free vectors only on top of the stack. For instance, the allocation of a vector is defined by: \begin{verbatim} def new_vect=incr(last_vect_) enddef; \end{verbatim} \noindent where \verb|last_vect_| is the index of the top of the stack. Hence, a vector is manipulated by its index on the stack. Writing \verb|v:=new_vect;| lets \verb|v| be the index of the newly allocated vector. Freeing a vector is also easy and is only allowed at the top of the stack: \begin{verbatim} def free_vect(expr i)= if i=last_vect_: last_vect_:=last_vect_-1; else: errmessage("Vector " & decimal i & " can't be freed!"); fi; enddef; \end{verbatim} How these macros are used is made explicit in the \verb|vect_rotate| macro which does a rotation of a vector \verb|v| around a vector \verb|axis| by an angle \verb|alpha|. This rotation is illustrated in figure~\ref{vector-rotation}. $\vec{v}$ is written as the sum of $\vec{h}$ and $\vec{a}$ where $\vec{h} \perp \vec{a}$. If $\vec{b}$ is $\overrightarrow{axis}/{\|\overrightarrow{axis}\|}$, $\vec{c}$ is computed as the vector product of $\vec{b}$ and $\vec{a}$ and $\vec{a}$ is then rotated in a simple way resulting in $\vec{f}$. The vectors declared with \verb|new_vect| are freed in the inverse order. The \verb|vect_rotate| macro makes use of a few other macros: \verb|vect_mod| computes the modulus of a vector; \verb|vect_dprod(a,b)| is the dot product of vectors \texttt{a} and \texttt{b}; \verb|vect_mult(b,a,x)| lets vector \texttt{b} equal vector \texttt{a} multiplied by the scalar \texttt{x}; \verb|vect_sum| and \verb|vect_diff| compute as their first argument the sum or the difference of the two other vectors; \verb|vect_prod(c,a,b)| lets vector \texttt{c} equal the vectorial product of vectors \texttt{a} and \texttt{b}. These macros are described in appendix A. %% fig2 \begin{figure*} \begin{center} \includegraphics{vect-fig.9}\hspace{1cm}\includegraphics{vect-fig.10} \end{center} \caption{Vector rotation}\label{vector-rotation} \end{figure*} \begin{verbatim} vardef vect_rotate(expr v,axis,alpha)= save v_a,v_b,v_c,v_d,v_e,v_f; v_a:=new_vect;v_b:=new_vect; v_c:=new_vect;v_d:=new_vect; v_e:=new_vect;v_f:=new_vect; v_g:=new_vect;v_h:=new_vect; vect_mult(v_b,axis,1/vect_mod(axis)); vect_mult(v_h,v_b,vect_dprod(v_b,v)); vect_diff(v_a,v,v_h); vect_prod(v_c,v_b,v_a); vect_mult(v_d,v_a,cosd(alpha)); vect_mult(v_e,v_c,sind(alpha)); vect_sum(v_f,v_d,v_e); vect_sum(v,v_f,v_h); free_vect(v_h);free_vect(v_g); free_vect(v_f);free_vect(v_e); free_vect(v_d);free_vect(v_c); free_vect(v_b);free_vect(v_a); enddef; \end{verbatim} The \verb|3d| package defines other macros in order to set the observer, %%%RF 1998/02/19: was `to manipulate' to compute a reference matrix, etc. Provision is given for manipulating objects. \subsection{Objects and classes} The \texttt{3d} package understands a notion of \emph{class}. A \emph{class} is a parameterized object. For instance, we have the class of regular tetrahedra, the class of regular cubes, etc. Our classes %%%RF 1998/02/19: `first level of genericity'->... abstraction are the lowest level of abstraction and classes can not be composed. They can only be \emph{instanciated}. When we need a specific tetrahedron, we call a generic function to create a tetrahedron, but with an identifier specific to one instance. A class is a set of vertices in space, together with a way to draw faces, and therefore edges. The author's focus was to manipulate (and later animate) polyhedra. As an example, the \verb|poly.mp| package provides the definition of each of the five regular convex polyhedra. %%%RF 1998/02/19: `is defining'->defines, etc, `both macros'->`each macro' Each class consists of two macros: one defines the points, the other calls the first macro and defines the faces. Each macro has a parameter which is a string identifying the particular instance of that class. \tolerance=4500 The points of a regular tetrahedron are defined in \verb|set_tetrahedron_points|, %%%RF 1998/02/19: second half sentence rewritten an example of the general macro name \verb|set_|\meta{class}\verb|_points|. Five points are defined, four of them with \verb|set_obj_point|, a macro which defines points \emph{local} to an object. The first four points are the vertices and the fifth is the center of the tetrahedron. \verb|set_obj_point|'s first parameter is the point number and the other three are the cartesian coordinates. The first three points are in a plane and the fourth is obtained with the \verb|new_face_point| macro, which folds a face %%%RF 1998/02/19: tagged reference sentence on to end in parens (see the description in appendix A for more details). The \verb|new_face_point| macro is used with the angle \verb|an| which is computed in advance. Once the four points are set, the object is normalized, which means that it is centered with respect to the list of vertices given as parameter (here \verb|1,2,3,4|) and the last vertex is put on a sphere of radius 1, centered on the origin. Therefore, point 5 is the center of the tetrahedron, and the tetrahedron is set symmetrically with respect to the origin. \tolerance=1000 %%%RF 1998/02/19: was `are hence inscriptible' All five convex regular polyhedra are defined in this way and may therefore be inscribed in a sphere of radius 1. \begin{verbatim} def set_tetrahedron_points(expr inst)= set_obj_point(1,0,0,0); set_obj_point(2,1,0,0); set_obj_point(3,cosd(60),sind(60),0); sinan=1/sqrt(3); cosan=sqrt(1-sinan**2); an=180-2*angle((cosan,sinan)); new_face_point(4,1,2,3,an); normalize_obj(inst)(1,2,3,4); set_obj_point(5,0,0,0); enddef; \end{verbatim} The second macro, \verb|def_tetrahedron| defines the number of points and faces of the instance, calls the previous macro and defines the faces with the macro \verb|set_obj_face|. The first argument of that macro is a \emph{local} face number, the second is a list of vertices such that the list goes clockwise when the face is visible. The last argument is the color of the face in RGB. \begin{verbatim} vardef def_tetrahedron(expr inst)= new_obj_points(inst,5); new_obj_faces(inst,4); set_tetrahedron_points(inst); set_obj_face(1,"1,2,4","b4fefe"); set_obj_face(2,"2,3,4","b49bc0"); set_obj_face(3,"1,4,3","b4c8fe"); set_obj_face(4,"1,3,2","b4fe40"); enddef; \end{verbatim} The result of the drawing is: \begin{center} \includegraphics{tetra.ps} \end{center} A more complex example is the icosahedron which is defined below. \begin{verbatim} def set_icosahedron_points(expr inst)= set_obj_point(1,0,0,0); set_obj_point(2,1,0,0); set_obj_point(3,cosd(60),sind(60),0); cosan=1-8/3*cosd(36)*cosd(36); sinan=sqrt(1-cosan*cosan); an=180-angle((cosan,sinan)); new_face_point(4,1,2,3,an); new_face_point(5,2,3,1,an); new_face_point(6,3,1,2,an); new_face_point(7,2,4,3,an); new_face_point(8,3,5,1,an); new_face_point(9,1,6,2,an); new_face_point(10,3,4,7,an); new_face_point(11,3,7,5,an); new_face_point(12,1,8,6,an); % 1 and 10 are opposite vertices normalize_obj(inst)(1,10); % center of icosahedron set_obj_point(13,0,0,0); enddef; \end{verbatim} \begin{verbatim} vardef def_icosahedron(expr inst)= save cosan,sinan,an; new_obj_points(inst,13); new_obj_faces(inst,20); set_icosahedron_points(inst); set_obj_face(1,"3,2,1","b40000"); set_obj_face(2,"2,3,4","ff0fa1"); set_obj_face(3,"3,7,4","b49b49"); set_obj_face(4,"3,5,7","b49bc0"); set_obj_face(5,"3,1,5","b4c8fe"); set_obj_face(6,"1,8,5","b4fefe"); set_obj_face(7,"1,6,8","b4fe40"); set_obj_face(8,"1,2,6","45d040"); set_obj_face(9,"2,9,6","45a114"); set_obj_face(10,"2,4,9","45a1d4"); set_obj_face(11,"9,4,10","4569d4"); set_obj_face(12,"4,7,10","112da1"); set_obj_face(13,"7,5,11","b4fefe"); set_obj_face(14,"5,8,11","b49bc0"); set_obj_face(15,"8,6,12","45a114"); set_obj_face(16,"6,9,12","b49b49"); set_obj_face(17,"8,12,11","b40000"); set_obj_face(18,"7,11,10","45a1d4"); set_obj_face(19,"12,10,11","b4c8fe"); set_obj_face(20,"9,10,12","ff0fa1"); enddef; \end{verbatim} Since all points of the objects are stored in a unique global array, they are internally accessed by the local numbers and an offset defined by the macro \verb|new_obj_points|. The icosahedron example shows a systematic use of the \verb|new_face_point| macro to compute a point on an adjacent face. Displaying such an icosahedron results in the figure~\ref{icosahedron}. %% fig3 \begin{figure}[h] \begin{center} \includegraphics{icosa.ps} \end{center} \caption{An icosahedron}\label{icosahedron} \end{figure} The other three regular convex polyhedra are: \begin{center} \includegraphics{cube.ps} \end{center} \begin{center} \includegraphics{octa.ps}\hfill\includegraphics{dodeca.ps} \end{center} The dodecahedron code is a bit special, since the vertices are built using ten additional points corresponding to face centers. These points are defined as an array of variables \verb|fc1| through \verb|fc10| with \verb|new_points(fc)(10)|. \verb|free_points(fc)(10)| frees them when they are no longer necessary. An excerpt of the dodecahedron code is: \begin{verbatim} def set_dodecahedron_points(expr inst)= new_points(fc)(10);% face centers set_point(fc1,0,0,0); set_obj_point(1,1,0,0); set_obj_point(2,cosd(72),sind(72),0); rotate_in_plane(3,fc1,1,2); ... free_points(fc)(10); enddef; \end{verbatim} Finally, wire drawings can be obtained by setting the boolean \verb|filled_faces| to false: \begin{center} \includegraphics{icosa-w.ps} \end{center} \subsection{Animating objects} The animation of one or several objects involves the object(s) and an observer. The animation is a set of images and from an image to the next one, the observer as well as the objects can move. For instance the macro \verb|one_image| in \verb|3d.mp| is: \begin{verbatim} def one_image(expr name,i,a,rd,ang)= beginfig(i); set_point(Obs, -rd*cosd(a*ang),-rd*sind(a*ang),1); Obs_phi:=90;Obs_dist:=2; % fix point 1 of object |name| point_of_view_obj(name,1,Obs_phi); draw_obj(name); rotate_obj_pv(name,1,vect_I,ang); % show the rotation point draw_point(name,1); draw_axes(red,green,blue); endfig; enddef; \end{verbatim} The parameters of this macro are a name of an object (\verb|name|), an image index (\verb|i|), and three values defining the position of the observer. The observer (\verb|Obs| is a global point and set with \verb|set_point|, not with \verb|set_obj_point|) follows a circle of radius \verb|rd|. %%%RF 1998/02/19: reversed `usually is' The parameter \verb|a|, which is usually a function of \verb|i|, determines the number of rotation steps of the observer, each step being a rotation of angle \verb|ang|. The distance between the observer and the projection plane is $2$ (see figure~\ref{proj-screen}). %% fig4 \begin{figure*} \begin{center} \includegraphics{vect-fig.8} \end{center} \caption{Projection on the screen}\label{proj-screen} \end{figure*} %% fig5 \begin{figure*} \begin{center} \includegraphics{vect-fig.16} \end{center} \caption{Orientation of the observer}\label{obs-orientation} \end{figure*} The orientation of the observer is defined by three angles (see figure~\ref{obs-orientation}). The \verb|Obs_phi| angle is given and the two others are computed with a call to \verb|point_of_view_obj(name,1,Obs_phi)| which constrains the observer to look towards point 1 of object \verb|name|. Therefore, this point will seem fixed on the animation and \verb|draw_point(name,1)| draws it later so that this feature can be observed. There is nothing special about that point, except that it remains fix when the object is rotated. The object is drawn with \verb|draw_obj(name)| and %%%RF 1998/02/19: `does rotate'->rotates -- these arguments aren't %%% described here, which seems wrong, but i don't think %%% it's easy to do better.... \verb|rotate_obj_pv| rotates the object \verb|name| by \verb|ang| degrees around an axis going through point 1 and directed by vector \verb|vect_I| ($\vec{\imath}$). The reference vectors ($\vec\imath$, $\vec\jmath$ and $\vec k$) are drawn in red, green and blue with \verb|draw_axes|. Finally, a complete animation of an icosahedron is obtained with \begin{verbatim} animate_object("icosahedron",1,100,100); \end{verbatim} %%%RF 1998/02/19: `and this'->`which' \noindent which generates files \verb|anim.101|, %%%RF 1998/02/19: `if ... is'->`from ...' \ldots, \verb|anim.200| from the main file \verb|anim.mp|. The first parameter of \verb|animate_object| is the name of the object to animate, the second and third parameters are minimal and maximal values of the index loop and the fourth parameter is an offset added to the index loop in order to get the file extension, which must lie in the interval $0..4096$. After each image is drawn, the values of the current bounding box are used to compute the bounding box of the sequence of images. The internal values \verb|xmin_|, \verb|ymin_|, \verb|xmax_| and \verb|ymax_| hold the minimal and maximal values of the coordinates of the past images' corners. They are updated just before each image is shipped out. \subsection{Putting the pieces together} Once all the views have been computed, they can be used separately (see for instance the five views of figure~\ref{anim-five-views}) or more interestingly, they can be merged. This task is made almost straightforward by \MP{} itself. Indeed, every time \verb|animate_object| is used, a shell script named \verb|create_animation.sh| is generated, as a side-effect of a call to \verb|show_animation_bbox|. The script is similar to that shown in figure~\ref{animation-script}. This script uses the values computed for the global bounding box of the sequence of images, for these values are necessary in order to extract the right parts of the images and get correct alignments; the parts are extracted with \texttt{pnmcut}.\footnote{One might think of using \texttt{Ghostscript} for generating an excerpt of an image, but if \texttt{Ghostscript} is used to generate the bounding box of an image, it will in general not be possible to have a good alignment between all images. The sizes of the excerpts are only known when all images have been produced.} If you have the programs used in this script (\texttt{Ghostscript}, etc.), you can just run it with \verb|sh create_animation.sh| on UNIX. You may need to adapt it to your needs, and for that purpose, you can modify the macro \verb|write_script| in \verb|3d.mp|. Some examples are included in the \verb|3d| distribution, and they can be viewed for instance with \texttt{netscape} or special programs such as \verb|xanim|. %% fig6 \begin{figure*} \includegraphics[scale=0.5]{anim.1}\hfill \includegraphics[scale=0.5]{anim.2}\hfill \includegraphics[scale=0.5]{anim.3}\hfill \includegraphics[scale=0.5]{anim.4}\hfill \includegraphics[scale=0.5]{anim.5} \caption{Five views of an animation}\label{anim-five-views} \end{figure*} \section{Future} It is quite easy to improve and extend the \verb|3d| macros but the author decided to go no further for the moment. Other objects can be implemented easily and new algorithms can be added. For instance in order to take light sources or shadows into account, one can compute the angles under which a face gets its light, and the angle under which this very face is seen, in order to decide how much darker or lighter it must be rendered. Another problem is to represent overlapping objects correctly. In the current implementation, each object is drawn independently from the other objects, so that the overlapping may be wrong. One solution is to sort all the faces according to their distance to the observer and, if two faces can not be ordered, to split them. Then, the faces can be drawn starting with the most distant, and ending with the closest one. Appendix B explains the internal representation of the objects and shows that this algorithm can be implemented without much surgery to the present code. \section{Acknowledgments} Thanks to John Hobby who always answers all my queries on the \MF{} mailing list. Thanks to Alain Filbois who helped me with the shell script syntax, to Thomas Lambolais and Thomas Genet who gave some feedback on this work, and to Dominique Larchey who pointed out a shortcoming in the conclusion. Thanks to Denis Barbier who was one of the first users of these macros and contributed the animated crayons in the distribution. Thanks to Bogus\l aw Jackowski who made valuable comments on some peculiarities of the code. And finally, special thanks to Ulrik Vieth who not only pushed me to polish my code and this paper more than I had first intended, but also made it possible to use \MP{} under \texttt{web2c}. \bibliography{paper} \appendix \section{Appendix A\\ Summary of the \texttt{3d} package} \subsection{Types} The commands in the \texttt{3d} package take parameters of several different types. The types are described here. \begin{itemize} \item An \AVN{} (\emph{Absolute Vector Number}) is the internal number identifying a vector in the \verb|vect| array (an integer). \item An \APN{} (\emph{Absolute Point Number}) refers to a vector in the same way as an \AVN{} (an integer). \item A \LPN{} (\emph{Local Point Number}) is a number identifying a point \emph{within} an object (an integer). Two \LPN{}s with the same value can correspond to different points in different objects. \item An \AFN{} (\emph{Absolute Face Number}) is the internal number identifying a face. \item A \LFN{} (\emph{Local Face Number}) is a number identifying a face \emph{within} an object (an integer). As for points, two \LFN{}s with the same value can correspond to different faces in different objects. \item A \CN{} (\emph{Class}) is a string representing a class, for instance \verb|"tetrahedron"|. It may only contain letters and underscores. \item An \IN{} (\emph{Object}) is a string representing an object, that is an instance of a class. Such a string may only contain letters and underscores. \item A \VL{} (\emph{Vertex List}) is a list of integers, where each integer identifies a vertex. For instance, \verb|1,7| is the list of vertices 1 and 7. \item A \VSL{} (\emph{Vertex String List}) is a string corresponding to a list of integers, where each integer identifies a vertex. For instance, \verb|"1,2,6,5"| is the list of vertices 1, 2, 6 and 5. \item \HEXCOL{} (\emph{Hex Color}) is a string representing a color with the three RGB components in hexadecimal and in the range $0..255$. For instance, \verb|"b4fe40"|. \item \COL{} (\emph{Color}) is a standard \MP{} color (a triplet of RGB components in the range $0..1$), such as \verb|red|. \item \STR{} (\emph{String}) is a string. \item \PAIR{} (\emph{Pair}) is a pair of numerics. \item \NUM{} (\emph{Numeric}) is a number. \item \BOOL{} (\emph{Boolean}) is a boolean. \end{itemize} \subsection{Low level vector commands} The low level vector commands define the classical operations in vector algebra. \begin{itemize} %\item \verb|vect_def(|\AVN\tc$x$\tc$y$\tc$z$\verb|)|; defines vector \AVN{} \item \verb|vect_def(|\AVN\tc$x$\tc$y$\tc$z$\verb|)|: defines vector \AVN{} as $(x,y,z)$; %\item \verb|set_point|: synonym of \verb|vect_def|; a point is stored in the \item \verb|set_point|; synonym of \verb|vect_def|: a point is stored in the same array as vectors. %\item \verb|set_obj_point(|\LPN\tc$x$\tc$y$\tc$z$\verb|)|; \item \verb|set_obj_point(|\LPN\tc$x$\tc$y$\tc$z$\verb|)|: this defines the point \LPN{} as $(x,y,z)$; %\item \verb|vect_def_vect(|\AVN$_1$\tc\AVN$_2$\verb|)|; \item \verb|vect_def_vect(|\AVN$_1$\tc\AVN$_2$\verb|)|: vector \AVN$_1$ becomes equal to vector \AVN$_2$; \item \verb|vect_sum(|\AVN$_1$\tc\AVN$_2$\tc\AVN$_3$\verb|)|: the vector \AVN$_1$ becomes the sum of vectors \AVN$_2$ and \AVN$_3$. \item \verb|vect_translate(|\AVN$_1$\tc\AVN$_2$\verb|)|: add vector \AVN$_2$ to vector \AVN$_1$; vector \AVN$_2$ remains unchanged. \item \verb|vect_diff(|\AVN$_1$\tc\AVN$_2$\tc\AVN$_3$\verb|)|: the vector \AVN$_1$ becomes the difference between vectors \AVN$_2$ and \AVN$_3$. \item \verb|vect_dprod(|\AVN$_1$\tc\AVN$_2$\verb|)| $\rightarrow $ \NUM{}: returns the dot product of vectors \AVN$_1$ and \AVN$_2$. \item \verb|vect_mod(|\AVN\verb|)| $\rightarrow $ \NUM{}: returns the modulus of vector \AVN. \item \verb|vect_prod(|\AVN$_1$\tc\AVN$_2$\tc\AVN$_3$\verb|)|: the vector \AVN$_1$ becomes the vector product of vectors \AVN$_2$ and \AVN$_3$. \tolerance=1000 \item \verb|vect_mult(|\AVN$_1$\tc\AVN$_2$\tc\NUM\verb|)|: \AVN$_1$ becomes vector \AVN$_2$ scaled by \NUM. \item \verb|mid_point(|\AVN$_1$\tc\AVN$_2$\tc\AVN$_3$\verb|)|: vector (or point) \AVN$_1$ becomes the mid-point of vectors (or of the line joining the points) \AVN$_2$ and \AVN$_3$. \item \verb|vect_rotate(|\AVN$_1$\tc\AVN$_2$\tc$a$\verb|)|: vector \AVN$_1$ is rotated around vector \AVN$_2$ by the angle $a$. \end{itemize} \tolerance=1000 \subsection{Operations on objects} Several operations apply globally on objects: \begin{itemize} \item \verb|assign_obj(|\IN\tc\CN\verb|)|: create \IN{} as an instance of class \CN. \item \verb|reset_obj(|\IN\verb|)|: put \IN{} back where it was just after it was initialized. \item \verb|put_obj(|\IN\tc\AVN\tc$s$\tc$\psi$\tc$\theta$\tc$\phi$\verb|)|: object \IN{} is scaled by $s$, shifted by vector \AVN{} and oriented with the angles $\psi$, $\theta$, $\phi$, as for the observer orientation (figure~\ref{obs-orientation}). \item \verb|rotate_obj_pv(|\IN\tc\LPN\tc\AVN\tc$a$\verb|)|: object \IN{} is rotated around an axis going through local point \LPN{} and directed by vector \AVN; the rotation is by $a$ degrees. \item \verb|rotate_obj_abs_pv(|\IN\tc\APN\tc\AVN\tc$a$\verb|)|:\break the object \IN{} is rotated around an axis going through absolute point \APN{} and directed by vector \AVN; the rotation is by $a$ degrees. \item \verb|rotate_obj_pp(|\IN\tc\LPN$_1$\tc\LPN$_2$\tc$a$\verb|)|: \IN{} is rotated around an axis going through local points \LPN$_1$ and \LPN$_2$; the rotation is by $a$ degrees. \item \verb|translate_obj(|\IN\tc\AVN\verb|)|: object \IN{} is translated by vector \AVN. \item \verb|scale_obj(|\IN\tc$v$\verb|)|: object~\IN{}~is~scaled~by~$v$. \end{itemize} \subsection{Building new points in space} Three macros are especially useful for the definition of regular polyhedra: \begin{itemize} \item \verb|rotate_in_plane(|$k$\tc$o$\tc$i$\tc$j$\verb|)|: get point $k$ from point $j$ by rotation around point $o$ by an angle $\alpha$ equal to the angle from $i$ to $j$; $i$, $j$ and $k$ are of type \LPN, whereas $o$ is of type \APN. \begin{center} \includegraphics{vect-fig.11} \end{center} \item \verb|new_face_point(|$c$\tc$o$\tc$i$\tc$j$\tc$\alpha$\verb|)|: the middle $m$ of points $i$ and $j$ is such that $\widehat{(\overrightarrow{om},\overrightarrow{mc})}=\alpha$ and $\overrightarrow{mc}$ is $\overrightarrow{om}$ rotated around $\overrightarrow{ji}$. $c$, $o$, $i$ and $j$ are of type \LPN. \begin{center} \includegraphics{vect-fig.7} \end{center} \item \verb|new_abs_face_point(|$c$\tc$o$\tc$i$\tc$j$\tc$\alpha$\verb|)|: similar to the previous definition, but $c$ and $o$ are of type \APN. \end{itemize} \subsection{Drawing points, axes, objects} \begin{itemize} \item \verb|draw_point(|\IN\tc\LPN\verb|)|: draw point \LPN{} in object \IN. \item \verb|draw_axes(|\COL$_1$\tc\COL$_2$\tc\COL$_3$\verb|)|: draw vectors $\vec\imath$, $\vec\jmath$ and $\vec{k}$ in colors \COL$_1$, \COL$_2$ and \COL$_3$. \item \verb|draw_obj(|\IN\verb|)|: draw object \IN. \end{itemize} \subsection{Setting faces} \begin{itemize} \item \verb|set_face(|\AFN\tc\VSL\tc\HEXCOL\verb|)|: set absolute face \AFN{} as delimited by the vertex list \VSL{} (local point numbers) and colored by color \HEXCOL. \item \verb|set_obj_face(|\LFN\tc\VSL\tc\HEXCOL\verb|)|: set local face \LFN{} as delimited by the vertex list \VSL{} (local point numbers) and colored by color \HEXCOL. \end{itemize} \break \subsection{View points, distance} \begin{itemize} \item \verb|compute_reference(|$\psi$\tc$\theta$\tc$\phi$\verb|)|: defines the orientation of the observer by the three angles $\psi$, $\theta$ and $\phi$. See figure~\ref{obs-orientation}. \item \verb|point_of_view_obj(|\IN\tc\LPN\tc$\phi$\verb|)|: the orientation of the observer is defined as looking local point \LPN{} of object \IN, with an angle of $\phi$; \item \verb|point_of_view_abs(|\APN\tc$\phi$\verb|)|: the observer's orientation is defined as looking absolute point \APN{}, with an angle of $\phi$; \item \verb|obs_distance(|$v$\verb|)(|\IN\tc\LPN\verb|)|: let $v$ equal the distance between the observer and local point \LPN{} in object \IN{}. \end{itemize} \subsection{Vector and point allocation} \begin{itemize} \item \verb|new_vect|$\rightarrow $ \AVN{}: return a new vector; \item \verb|new_point|: synonym of \verb|new_vect|; \item \verb|new_points(|$v$\verb|)(|$n$\verb|)|: defines the absolute points $v_1, \ldots, v_n$, using \verb|new_point|; \item \verb|free_vect(|\AVN\verb|)|: free vector \AVN; \item \verb|free_point(|\APN\verb|)|: free point \APN; \item \verb|free_points(|$v$\verb|)(|$n$\verb|)|: frees the absolute points $v_1, \ldots, v_n$, using \verb|free_point|. \end{itemize} \subsection{Debugging} \begin{itemize} \item \verb|show_vect(|\STR\tc\AVN\verb|)|: shows vector \AVN, with string \STR. \item \verb|show_point|: synonym of \verb|show_vect| \item \verb|show_pair(|\STR\tc\PAIR\verb|)|: this shows a numeric pair, with string \STR. \end{itemize} \subsection{Normalization} \begin{itemize} \item \verb|normalize_obj(|\IN\tc\VL\verb|)|: normalize object \IN{} with respect to the list of vertices \VL. \end{itemize} \subsection{Parameters} \begin{itemize} \item \verb|Obs_dist| $\rightarrow$ \NUM: distance between the observer and the projection plane. \item \verb|h_field| $\rightarrow$ \NUM: horizontal field of view (default: 100 degrees) \item \verb|v_field| $\rightarrow$ \NUM: vertical field of view (default: 70 degrees) \item \verb|Obs_phi| $\rightarrow$ \NUM: angle $\phi$ for the orientation of the observer; \item \verb|Obs_theta| $\rightarrow$ \NUM: angle $\theta$ for the orientation of the observer; \item \verb|Obs_psi| $\rightarrow$ \NUM: angle $\psi$ for the orientation of the observer; \item \verb|drawing_scale| $\rightarrow$ \NUM: scale factor applied for drawing; \item \verb|filled_faces| $\rightarrow$ \BOOL: if \texttt{true}, the faces are drawn filled; if \texttt{false}, only the edges are drawn, and hidden edges are drawn dashed; \item \verb|draw_contours| $\rightarrow$ \BOOL: if \texttt{true}, the contours of the faces are drawn, and the lines have the thickness \verb|contour_width|; if \texttt{false}, the contours are not drawn; \item \verb|contour_width| $\rightarrow$ \NUM: dimension used for drawing contours of faces (default: 1pt). \end{itemize} \subsection{Constants} These values represent constant objects such as reference vectors, and should not be changed. \begin{itemize} \item \verb|vect_null| $\rightarrow$ \AVN: internal index for $\vec0$. \item \verb|vect_I| $\rightarrow$ \AVN: internal index for $\vec\imath$. \item \verb|vect_J| $\rightarrow$ \AVN: internal index for $\vec\jmath$. \item \verb|vect_K| $\rightarrow$ \AVN: internal index for $\vec k$. \item \verb|point_null| $\rightarrow$ \APN: internal index for $\vec0$. \item \verb|Obs| $\rightarrow$ \APN: observer's internal point number. \end{itemize} \subsection{Defining new object points and faces} \begin{itemize} \item \verb|new_obj_points(|\IN\tc\NUM\verb|)|: defines points $1$ to \NUM{} in object \IN; must be used before setting the points; \item \verb|new_obj_faces(|\IN\tc\NUM\verb|)|: defines \NUM{} faces in object \IN; must be used before setting the faces; \end{itemize} \subsection{Offsets} \begin{itemize} \item \verb|pnt(|\LPN\verb|)| $\rightarrow$ \APN: returns the absolute point number for a given local point index. \item \verb|face(|\LFN\verb|)| $\rightarrow$ \AFN: returns the absolute face number for a given local face index. \end{itemize} \subsection{Standard classes} Five standard classes are defined in \texttt{poly.mp}: they define the five regular convex polyhedra. For each class \meta{class}, there are two macros: \begin{itemize} \item \verb|set_|\meta{class}\verb|_points| (e.g. \verb|set_cube_points|) \item \verb|def_|\meta{class} (e.g. \verb|def_cube|) \end{itemize} Each of these macros is defined with a parameter which is the instance name. \subsection{Standard animations} The \texttt{3d} package provides a few standard animations using the convex polyhedra. In each of these animations, the observer follows a circular path pictured in figure~\ref{observer-motion}. Each standard animation is divided into two macros. The first, such as \verb|animate_object|, defines the class(es) that are used and sets the objects. The second, such as \verb|one_image|, sets the observer, draws the object(s) and moves the object(s) and the observer. The file \verb|animpoly.mp| gives examples of the use of the standard animations. \iffalse \begin{itemize} \item \verb|one_image(name,i,a)| \item \verb|one_image_two_objects(name_a,name_b,i,a)| \item \verb|one_image_three_objects(name_a,name_b,name_c,i,a)| \item \verb|one_image_two_identical_objects(name_a,name_b,i,a)| \item \verb|animate_object(name,imin,imax,index)| \item \verb|animate_two_objects(name_a,name_b,imin,imax,index)| \item \verb|animate_three_objects(name_a,name_b,name_c,imin,imax,index)| \item \verb|animate_two_identical_objects(name,imin,imax,index)| \end{itemize} \fi %% fig7 \begin{figure*} \begin{center} \includegraphics{vect-fig.17} \end{center} \caption{Motion of the observer}\label{observer-motion} \end{figure*} \section{Appendix B\\ Coding an object} In order to extend the \texttt{3d} package, it is necessary to understand how the objects are coded. We give here an overview of this coding, but the reader is advised to peek in the code to get a better understanding on how all the functions interact. First, an object has a name, for instance \verb|"box"|. The macro \verb|box_class| (which can be called with \verb|obj_class_("box")|) is the string corresponding to the class of \verb|"box"|, for instance \verb|"cube"|. The variable \verb|cube_point_offsetbox|, of type \texttt{numeric}, and obtained with \verb|obj_point_offset_("box")|, is equal to the absolute index of the last point of the previous object. A cube is defined with $8+1$ points. Assuming it was defined after an icosahedron ($12+1$ points) named \verb|"ico"|, \verb|cube_point_offsetbox| will be a \verb|numeric| equal to $13$. \verb|cube_pointsbox| (obtained with \verb|obj_points_("box")|) is a macro equal to $9$. %(\verb|current_point_offset_| is equal to \verb|cube_point_offsetbox|.) The variable \verb|cube_face_offsetbox|, similar to \verb|cube_point_offsetbox|, obtained with a call to \verb|obj_face_offset_("box")|, equals $20$. The macro \verb|cube_facesbox| (obtained by \verb|obj_faces_("box")|) is equal to $6$. %(\verb|current_face_offset_| is equal to \verb|cube_face_offsetbox|.) The \verb|obj_name| macro is extended each time a new object is defined. To an absolute face number, it associates an object name. Hence, it is possible to go through all faces. \verb|last_point_offset_| and \verb|last_face_offset_| are the absolute numbers of the last points and faces defined up to now. \begin{verbatim} def obj_name(expr i)= if i<1: elseif i<=20:"ico" elseif i<=26:"box" fi; enddef; \end{verbatim} \verb|pnt(i)| gives the absolute vector corresponding to local point $i$. \verb|ipnt_(i)| is the absolute point number, that is $i$ plus the number of points defined beforehand in other objects. \verb|points_[j]| is the absolute vector corresponding to absolute object point $j$. Similarly, \verb|face(i)| is the absolute face corresponding to local face $i$. The list of vertices of absolute face number $i$ is \verb|face_points_[i]|. The color of absolute face number $i$ is \verb|face_color_[i]|. When the macros \verb|pnt| or \verb|face| are to be used, the calls \verb|define_current_point_offset_("box")| and \verb|define_current_face_offset_("box")| must be issued. \makesignature \end{document}