summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/metapost/mp3d/3d.mp
diff options
context:
space:
mode:
Diffstat (limited to 'Master/texmf-dist/metapost/mp3d/3d.mp')
-rw-r--r--Master/texmf-dist/metapost/mp3d/3d.mp1259
1 files changed, 1259 insertions, 0 deletions
diff --git a/Master/texmf-dist/metapost/mp3d/3d.mp b/Master/texmf-dist/metapost/mp3d/3d.mp
new file mode 100644
index 00000000000..7335a384d2c
--- /dev/null
+++ b/Master/texmf-dist/metapost/mp3d/3d.mp
@@ -0,0 +1,1259 @@
+%%\input epsf
+%%\def\newpage{\vfill\eject}
+%%\advance\vsize1in
+%%\let\ora\overrightarrow
+%%\def\title#1{\hrule\vskip1mm#1\par\vskip1mm\hrule\vskip5mm}
+%%\def\figure#1{\par\centerline{\epsfbox{#1}}}
+%%\title{{\bf 3D.MP: 3-DIMENSIONAL REPRESENTATIONS IN METAPOST}}
+
+%% version 1.34, 17 August 2003
+%% {\bf Denis Roegel} ({\tt roegel@loria.fr})
+
+%% This package provides definitions enabling the manipulation
+%% and animation of 3-dimensional objects.
+%% Such objects can be included in a \TeX{} file or used on web pages
+%% for instance. See the documentation enclosed in the distribution for
+%% more details.
+
+%% Thanks to John Hobby and Ulrik Vieth for helpful hints.
+
+%% PROJECTS FOR THE FUTURE:
+
+%% $-$ take light sources into account and show shadows and darker faces
+
+%% $-$ handle overlapping of objects ({\it obj\_name\/} can be used when
+%% going through all faces)
+
+if known three_d_version:
+ expandafter endinput % avoids loading this package twice
+fi;
+
+message "*** 3d, v1.34 (c) D. Roegel, 17 August 2003 ***";
+numeric three_d_version;
+three_d_version=1.34;
+
+% This package needs |3dgeom| in a few places. |3dgeom| also loads |3d|
+% but that's not a problem.
+%
+input 3dgeom;
+
+%%\newpage
+%%\title{Vector operations}
+
+% components of vector |i|
+def xval(expr i)=vec[i]x enddef;
+def yval(expr i)=vec[i]y enddef;
+def zval(expr i)=vec[i]z enddef;
+
+% vector (or point) equality (absolute version)
+def vec_eq_(expr i,j)=
+ ((xval(i)=xval(j)) and (yval(i)=yval(j)) and (zval(i)=zval(j)))
+enddef;
+
+% vector (or point) equality (local version)
+def vec_eq(expr i,j)=vec_eq_(pnt(i),pnt(j)) enddef;
+
+% vector inequality (absolute version)
+def vec_neq_(expr i,j)=(not vec_eq_(i,j)) enddef;
+
+% vector inequality (local version)
+def vec_neq(expr i,j)=(not vec_eq(i,j)) enddef;
+
+% definition of vector |i| by its coordinates (absolute version)
+def vec_def_(expr i,xi,yi,zi)= vec[i]x:=xi;vec[i]y:=yi;vec[i]z:=zi; enddef;
+
+% definition of vector |i| by its coordinates (local version)
+def vec_def(expr i,xi,yi,zi)= vec_def_(pnt(i),xi,yi,zi) enddef;
+
+% a point is stored as a vector (absolute version)
+let set_point_ = vec_def_;
+
+% a point is stored as a vector (local version)
+let set_point = vec_def;
+
+def set_point_vec_(expr i,v)=
+ set_point_(i,xval(v),yval(v),zval(v))
+enddef;
+
+def set_point_vec(expr i,v)=set_point_vec_(pnt(i),v) enddef;
+
+let vec_def_vec_=set_point_vec_;
+let vec_def_vec=set_point_vec;
+
+% vector sum: |vec[k]| $\leftarrow$ |vec[i]|$+$|vec[j]| (absolute version)
+def vec_sum_(expr k,i,j)=
+ vec[k]x:=vec[i]x+vec[j]x;
+ vec[k]y:=vec[i]y+vec[j]y;
+ vec[k]z:=vec[i]z+vec[j]z;
+enddef;
+
+% vector sum: |vec[k]| $\leftarrow$ |vec[i]|$+$|vec[j]| (local version)
+def vec_sum(expr k,i,j)=vec_sum_(pnt(k),pnt(i),pnt(j)) enddef;
+
+% vector translation: |vec[i]| $\leftarrow$ |vec[i]|$+$|vec[v]|
+def vec_translate_(expr i,v)=vec_sum_(i,i,v) enddef;
+
+% Here, the second parameter is absolute, because this is probably
+% the most common case.
+def vec_translate(expr i,v)=vec_translate_(pnt(i),v) enddef;
+
+% vector difference: |vec[k]| $\leftarrow$ |vec[i]|$-$|vec[j]|
+def vec_diff_(expr k,i,j)=
+ vec[k]x:=vec[i]x-vec[j]x;
+ vec[k]y:=vec[i]y-vec[j]y;
+ vec[k]z:=vec[i]z-vec[j]z;
+enddef;
+
+def vec_diff(expr k,i,j)=vec_diff_(pnt(k),pnt(i),pnt(j)) enddef;
+
+% dot product of |vec[i]| and |vec[j]|
+vardef vec_dprod_(expr i,j)=
+ (vec[i]x*vec[j]x+vec[i]y*vec[j]y+vec[i]z*vec[j]z)
+enddef;
+
+vardef vec_dprod(expr i,j)=vec_dprod_(pnt(i),pnt(j)) enddef;
+
+% modulus of |vec[i]|, absolute version
+% In the computation, we try to avoid overflows or underflows;
+% we perform a scaling in order to avoid losing too much
+% information in certain cases
+vardef vec_mod_(expr i)=
+ save prod,m_;
+ hide(
+ new_vec(v_a);
+ m_=max(abs(xval(i)),abs(yval(i)),abs(zval(i)));
+ if m_>0:vec_mult_(v_a,i,1/m_);else:vec_def_vec_(v_a,vec_null);fi;
+ prod=m_*sqrt(vec_dprod_(v_a,v_a));
+ free_vec(v_a);
+ )
+ prod
+enddef;
+
+% modulus of |vec[i]|, local version
+% If the return value must be compared to 0,
+% use |vec_eq| with |vec_null| instead.
+vardef vec_mod(expr i)= vec_mod_(pnt(i)) enddef;
+
+% unit vector |vec[i]| corresponding to vector |vec[j]|
+% only non-null vectors are changed
+def vec_unit_(expr i,j)=
+ if vec_mod_(j)>0: vec_mult_(i,j,1/vec_mod_(j));
+ else:vec_def_vec_(i,j);
+ fi;
+enddef;
+
+def vec_unit(expr i,j)=vec_unit_(pnt(i),pnt(j)) enddef;
+
+% vector product: |vec[k]| $\leftarrow$ |vec[i]| $\land$ |vec[j]|
+def vec_prod_(expr k,i,j)=
+ vec[k]x:=vec[i]y*vec[j]z-vec[i]z*vec[j]y;
+ vec[k]y:=vec[i]z*vec[j]x-vec[i]x*vec[j]z;
+ vec[k]z:=vec[i]x*vec[j]y-vec[i]y*vec[j]x;
+enddef;
+
+def vec_prod(expr k,i,j)=vec_prod_(pnt(k),pnt(i),pnt(j)) enddef;
+
+% scalar multiplication: |vec[j]| $\leftarrow$ |vec[i]*v| (absolute version)
+def vec_mult_(expr j,i,v)=
+ vec[j]x:=v*vec[i]x;vec[j]y:=v*vec[i]y;vec[j]z:=v*vec[i]z;
+enddef;
+
+% scalar multiplication: |vec[j]| $\leftarrow$ |vec[i]*v| (local version)
+def vec_mult(expr j,i,v)=vec_mult_(pnt(j),pnt(i),v) enddef;
+
+% middle of two points (absolute version)
+def mid_point_(expr k,i,j)= vec_sum_(k,i,j);vec_mult_(k,k,.5); enddef;
+
+% middle of two points (local version)
+def mid_point(expr k,i,j)= mid_point_(pnt(k),pnt(i),pnt(j)); enddef;
+
+%%\newpage
+%%\title{Vector rotation}
+% Rotation of |vec[v]| around |vec[axis]| by an angle |alpha|
+
+%% The vector $\vec{v}$ is first projected on the axis
+%% giving vectors $\vec{a}$ and $\vec{h}$:
+%%\figure{vect-fig.9}
+%% If we set
+%% $\vec{b}={\ora{axis}\over \left\Vert\vcenter{\ora{axis}}\right\Vert}$,
+%% the rotated vector $\vec{v'}$ is equal to $\vec{h}+\vec{f}$
+%% where $\vec{f}=\cos\alpha \cdot \vec{a} + \sin\alpha\cdot \vec{c}$.
+%% and $\vec{h}=(\vec{v}\cdot\vec{b})\vec{b}$
+%%\figure{vect-fig.10}
+
+% The rotation is independent of |vec[axis]|'s module.
+% |v| = old and new vector
+% |axis| = rotation axis
+% |alpha| = rotation angle
+%
+vardef vec_rotate_(expr v,axis,alpha)=
+ new_vec(v_a);new_vec(v_b);new_vec(v_c);
+ new_vec(v_d);new_vec(v_e);new_vec(v_f);
+ new_vec(v_g);new_vec(v_h);
+ vec_mult_(v_b,axis,1/vec_mod_(axis));
+ vec_mult_(v_h,v_b,vec_dprod_(v_b,v)); % projection of |v| on |axis|
+ vec_diff_(v_a,v,v_h);
+ vec_prod_(v_c,v_b,v_a);
+ vec_mult_(v_d,v_a,cosd(alpha));
+ vec_mult_(v_e,v_c,sind(alpha));
+ vec_sum_(v_f,v_d,v_e);
+ vec_sum_(v,v_f,v_h);
+ free_vec(v_h);free_vec(v_g);
+ free_vec(v_f);free_vec(v_e);free_vec(v_d);
+ free_vec(v_c);free_vec(v_b);free_vec(v_a);
+enddef;
+
+% The second parameter is left absolute because this is probably the most
+% common case.
+vardef vec_rotate(expr v,axis,alpha)=vec_rotate_(pnt(v),axis,alpha) enddef;
+
+%%\newpage
+%%\title{Operations on objects}
+% |iname| is the handler for an instance of an object of class |name|
+% |iname| must be a letter string
+% |vardef| is not used because at some point we give other names
+% to |assign_obj| with |let| and this cannot be done with |vardef|.
+% (see MFbook for details)
+def assign_obj(expr iname,name)=
+ begingroup
+ save tmpdef;
+ string tmpdef; % we need to add double quotes (char 34)
+ tmpdef="def " & iname & "_class=" & ditto & name & ditto & " enddef";
+ scantokens tmpdef;
+ def_obj(iname);
+ endgroup
+enddef;
+
+% |name| is the the name of an object instance
+% It must be made only of letters (or underscores), but no digits.
+def def_obj(expr name)=
+ scantokens begingroup
+ save tmpdef;string tmpdef;
+ tmpdef="def_" & obj_class_(name) & "(" & ditto & name & ditto & ")";
+ tmpdef
+ endgroup
+enddef;
+
+% This macro puts an object back where it was right at the beginning,
+% or rather, where the |set| definition puts it (which may be different
+% than the initial position, in case it depends on parameters).
+% |iname| is the name of an object instance.
+vardef reset_obj(expr iname)=
+ save tmpdef;
+ string tmpdef;
+ define_current_point_offset_(iname);
+ tmpdef="set_" & obj_class_(iname) & "_points";
+ scantokens tmpdef(iname);
+enddef;
+
+% Put an object at position given by |pos| (a vector) and
+% with orientations given by angles |psi|, |theta|, |phi|.
+% The object is scaled by |scale|.
+% |iname| is the name of an object instance.
+% If the shape of the object has been changed since it was
+% created, these changes are lost.
+vardef put_obj(expr iname,pos,scale,psi,theta,phi)=
+ reset_obj(iname);scale_obj(iname,scale);
+ new_vec(v_x);new_vec(v_y);new_vec(v_z);
+ vec_def_vec_(v_x,vec_I);
+ vec_def_vec_(v_y,vec_J);
+ vec_def_vec_(v_z,vec_K);
+ rotate_obj_abs_pv(iname,point_null,v_z,psi);
+ vec_rotate_(v_x,v_z,psi);vec_rotate_(v_y,v_z,psi);
+ rotate_obj_abs_pv(iname,point_null,v_y,theta);
+ vec_rotate_(v_x,v_y,theta);vec_rotate_(v_z,v_y,theta);
+ rotate_obj_abs_pv(iname,point_null,v_x,phi);
+ vec_rotate_(v_y,v_x,phi);vec_rotate_(v_z,v_x,phi);
+ free_vec(v_z);free_vec(v_y);free_vec(v_x);
+ translate_obj(iname,pos);
+enddef;
+
+%%\newpage
+%%\title{Rotation, translation and scaling of objects}
+% Rotation of an object instance |name| around an axis
+% going through a point |p| (local to the object)
+% and directed by vector |vec[v]|. The angle of rotation is |a|.
+vardef rotate_obj_pv(expr name,p,v,a)=
+ define_current_point_offset_(name);
+ rotate_obj_abs_pv(name,pnt(p),v,a);
+enddef;
+
+vardef rotate_obj_abs_pv(expr name,p,v,a)=
+ define_current_point_offset_(name);
+ new_vec(v_a);
+ for i:=1 upto obj_points_(name):
+ vec_diff_(v_a,pnt(i),p);
+ vec_rotate_(v_a,v,a);
+ vec_sum_(pnt(i),v_a,p);
+ endfor;
+ free_vec(v_a);
+enddef;
+
+% Rotation of an object instance |name| around an axis
+% going through a point |p| (local to the object)
+% and directed by vector $\ora{pq}$. The angle of rotation is |a|.
+vardef rotate_obj_pp(expr name,p,q,a)=
+ define_current_point_offset_(name);
+ new_vec(v_a);new_vec(axis);
+ vec_diff_(axis,pnt(q),pnt(p));
+ for i:=1 upto obj_points_(name):
+ vec_diff_(v_a,pnt(i),pnt(p));
+ vec_rotate_(v_a,axis,a);
+ vec_sum_(pnt(i),v_a,pnt(p));
+ endfor;
+ free_vec(axis);free_vec(v_a);
+enddef;
+
+% Translation of an object instance |name| by a vector |vec[v]|.
+vardef translate_obj(expr name,v)=
+ define_current_point_offset_(name);
+ for i:=1 upto obj_points_(name):
+ vec_sum_(pnt(i),pnt(i),v);
+ endfor;
+enddef;
+
+% Scalar multiplication of an object instance |name| by a scalar |v|.
+vardef scale_obj(expr name,v)=
+ define_current_point_offset_(name);
+ for i:=1 upto obj_points_(name):
+ vec_mult(i,i,v);
+ endfor;
+enddef;
+
+
+%%\newpage
+%%\title{Functions to build new points in space}
+% Rotation in a plane: this is useful to define a regular polygon.
+% |k| is a new point obtained from point |j| by rotation around |o|
+% by a angle $\alpha$ equal to the angle from |i| to |j|.
+%%\figure{vect-fig.11}
+vardef rotate_in_plane_(expr k,o,i,j)=
+ save cosalpha,sinalpha,alpha;
+ new_vec(v_a);new_vec(v_b);new_vec(v_c);
+ vec_diff_(v_a,i,o);vec_diff_(v_b,j,o);vec_prod_(v_c,v_a,v_b);
+ cosalpha=vec_dprod_(v_a,v_b)/vec_mod_(v_a)/vec_mod_(v_b);
+ sinalpha=sqrt(1-cosalpha**2);
+ alpha=angle((cosalpha,sinalpha));
+ vec_rotate_(v_b,v_c,alpha);
+ vec_sum_(k,o,v_b);
+ free_vec(v_c);free_vec(v_b);free_vec(v_a);
+enddef;
+
+vardef rotate_in_plane(expr k,o,i,j)=
+ rotate_in_plane_(pnt(k),o,pnt(i),pnt(j))
+enddef;
+
+% Build a point on a adjacent face.
+%% The middle $m$ of points $i$ and $j$ is such that
+%% $\widehat{(\ora{om},\ora{mc})}=\alpha$
+%% This is useful to define regular polyhedra
+%%\figure{vect-fig.7}
+vardef new_face_point_(expr c,o,i,j,alpha)=
+ new_vec(v_a);new_vec(v_b);new_vec(v_c);new_vec(v_d);new_vec(v_e);
+ vec_diff_(v_a,i,o);vec_diff_(v_b,j,o);
+ vec_sum_(v_c,v_a,v_b);
+ vec_mult_(v_d,v_c,.5);
+ vec_diff_(v_e,i,j);
+ vec_sum_(c,v_d,o);
+ vec_rotate_(v_d,v_e,alpha);
+ vec_sum_(c,v_d,c);
+ free_vec(v_e);free_vec(v_d);free_vec(v_c);free_vec(v_b);free_vec(v_a);
+enddef;
+
+vardef new_face_point(expr c,o,i,j,alpha)=
+ new_face_point_(pnt(c),pnt(o),pnt(i),pnt(j),alpha)
+enddef;
+
+vardef new_abs_face_point(expr c,o,i,j,alpha)=
+ new_face_point_(c,o,pnt(i),pnt(j),alpha)
+enddef;
+
+%%\newpage
+%%\title{Computation of the projection of a point on the ``screen''}
+% |p| is the projection of |m|
+% |m| = point in space (3 coordinates)
+% |p| = point of the intersection plane
+%%\figure{vect-fig.8}
+vardef project_point(expr p,m)=
+ save tmpalpha;
+ new_vec(v_a);new_vec(v_b);
+ if projection_type=2: % oblique
+ if point_in_plane_p_pl_(m)(projection_plane):
+ % |m| is on the projection plane
+ vec_diff_(v_a,m,ObliqueCenter_);
+ y[p]:=drawing_scale*vec_dprod_(v_a,ProjJ_);
+ x[p]:=drawing_scale*vec_dprod_(v_a,ProjK_);
+ else: % |m| is not on the projection plane
+ new_line_(l)(m,ObliqueCenter_);
+ vec_diff_(l2,l2,Obs);
+ vec_sum_(l2,l2,m);
+ % (the direction does not depend on Obs)
+ if def_inter_p_l_pl_(v_a)(l)(projection_plane):
+ vec_diff_(v_a,v_a,ObliqueCenter_);
+ y[p]:=drawing_scale*vec_dprod_(v_a,ProjJ_);
+ x[p]:=drawing_scale*vec_dprod_(v_a,ProjK_);
+ else: message "Point " & decimal m & " cannot be projected";
+ x[p]:=too_big_;y[p]=too_big_;
+ fi;
+ free_line(l);
+ fi;
+ else:
+ vec_diff_(v_b,m,Obs); % vector |Obs|-|m|
+ % |vec[v_a]| is |vec[v_b]| expressed in (|ObsI_|,|ObsJ_|,|ObsK_|)
+ % coordinates.
+ vec[v_a]x:=vec[IObsI_]x*vec[v_b]x
+ +vec[IObsJ_]x*vec[v_b]y+vec[IObsK_]x*vec[v_b]z;
+ vec[v_a]y:=vec[IObsI_]y*vec[v_b]x
+ +vec[IObsJ_]y*vec[v_b]y+vec[IObsK_]y*vec[v_b]z;
+ vec[v_a]z:=vec[IObsI_]z*vec[v_b]x
+ +vec[IObsJ_]z*vec[v_b]y+vec[IObsK_]z*vec[v_b]z;
+ if vec[v_a]x<Obs_dist: % then, point |m| is too close
+ message "Point " & decimal m & " too close -> not drawn";
+ x[p]:=too_big_;y[p]=too_big_;
+ else:
+ if (angle(vec[v_a]x,vec[v_a]z)>h_field/2)
+ or (angle(vec[v_a]x,vec[v_a]y)>v_field/2):
+ message "Point " & decimal m & " out of screen -> not drawn";
+ x[p]:=too_big_;y[p]=too_big_;
+ else:
+ if projection_type=0: % central perspective
+ tmpalpha:=Obs_dist/vec[v_a]x;
+ else:
+ tmpalpha:=1; % parallel
+ fi;
+ y[p]:=drawing_scale*tmpalpha*vec[v_a]y;
+ x[p]:=drawing_scale*tmpalpha*vec[v_a]z;
+ fi;
+ fi;
+ fi;
+ free_vec(v_b);free_vec(v_a);
+enddef;
+
+% At some point, we may need to do an oblique projection
+% of vectors |ObsK_| and |ObsI_| on a plane, and to normalize
+% and orthogonalize the projections (with the projection of |ObsK_|
+% keeping the same direction). This is done here,
+% where we take two vectors, a direction (line) and
+% a plane, and return two vectors. This function assumes
+% there is an intersection between line |l| and plane |p|.
+% We do not test it here.
+
+vardef project_vectors(expr va,vb)(expr k,i)(text l)(text p)=
+ save vc;new_vec(vc);
+ if proj_v_v_l_pl_(va,k)(l)(p): % |va| is the projection of vector |k|
+ else: message "THIS SHOULD NOT HAPPEN";
+ fi;
+ if proj_v_v_l_pl_(vb,i)(l)(p): % |vb| is the projection of vector |i|
+ else: message "THIS SHOULD NOT HAPPEN";
+ fi;
+ % now, we orthonormalize these vectors:
+ vec_prod_(vc,va,vb);
+ vec_unit_(va,va);vec_unit_(vc,vc);vec_prod_(vb,vc,va);
+ free_vec(vc);
+enddef;
+
+% Object projection
+% This is a mere iteration on |project_point|
+def project_obj(expr name)=
+ define_current_point_offset_(name);
+ for i:=1 upto obj_points_(name):
+ project_point(ipnt_(i),pnt(i));endfor;
+enddef;
+
+% Projection screen
+vardef show_projection_screen=
+ save dx,dy;
+ dx=Obs_dist*sind(h_field/2)/cosd(h_field/2);
+ dy=Obs_dist*sind(v_field/2)/cosd(v_field/2);
+ new_vec(pa);new_vec(pb);new_vec(pc);new_vec(pd);new_vec(op);
+ new_vec(w);new_vec(h);
+ vec_mult_(op,ObsI_,Obs_dist);vec_sum_(op,op,Obs); % center of screen
+ vec_mult_(w,ObsK_,dx);vec_mult_(h,ObsJ_,dy);
+ vec_sum_(pa,op,w);vec_sum_(pa,pa,h); % upper right corner
+ vec_mult_(w,w,-2);vec_mult_(h,h,-2);
+ vec_sum_(pb,pa,w);vec_sum_(pc,pb,h);vec_sum_(pd,pa,h);
+ message "Screen at corners:";
+ show_point("urcorner: ",pa);
+ show_point("ulcorner: ",pb);
+ show_point("llcorner: ",pc);
+ show_point("lrcorner: ",pd);
+ show_point("Obs:",Obs);
+ free_vec(h);free_vec(w);
+ free_vec(op);free_vec(pd);free_vec(pc);free_vec(pb);free_vec(pa);
+enddef;
+
+
+%%\newpage
+%%\title{Draw one face, hiding it if it is hidden}
+% The order of the vertices determines what is the visible side
+% of the face. The order must be clockwise when the face is seen.
+% |drawhidden| is a boolean; if |true| only hidden faces are drawn; if |false|,
+% only visible faces are drawn. Therefore, |draw_face| is called twice
+% by |draw_faces|.
+vardef draw_face(text vertices)(expr col,drawhidden)=
+ save p,num,overflow,i,j,k,nv;
+ path p;boolean overflow;
+ overflow=false;
+ forsuffixes $=vertices:
+ if z[ipnt_($)]=(too_big_,too_big_):overflow:=true; fi;
+ exitif overflow;
+ endfor;
+ if overflow: message "Face can not be drawn, due to overflow";
+ else:
+ p=forsuffixes $=vertices:z[ipnt_($)]--endfor cycle;
+ % we do now search for three distinct and non-aligned suffixes:
+ % usually, the first three suffixes do
+ new_vec(normal_vec);new_vec(v_a);new_vec(v_b);new_vec(v_c);
+ % first, we copy all the indexes in an array, so that
+ % it is easier to go through them
+ i=1; % num0 is not used
+ forsuffixes $=vertices:num[i]=$;i:=i+1;endfor;
+ nv=i-1;
+ for $:=1 upto nv:
+ for $$:=$+1 upto nv:
+ for $$$:=$$+1 upto nv:
+ vec_diff_(v_a,pnt(num[$$]),pnt(num[$]));
+ vec_diff_(v_b,pnt(num[$$$]),pnt(num[$$]));
+ vec_prod_(normal_vec,v_a,v_b);
+ exitif vec_neq_(normal_vec,vec_null);
+ % |vec_mod_| must not be used for such a test
+ endfor;
+ exitif vec_neq_(normal_vec,vec_null);
+ endfor;
+ exitif vec_neq_(normal_vec,vec_null);
+ endfor;
+ if projection_type=0: % perspective
+ vec_diff_(v_c,pnt(num1),Obs);
+ else: % parallel
+ vec_def_vec_(v_c,ObsI_);
+ fi;
+ if filled_faces:
+ if vec_dprod_(normal_vec,v_c)<0:
+ fill p withcolor col;drawcontour(p,contour_width,contour_color)();
+ else: % |draw p dashed evenly;| if this is done, you must ensure
+ % that hidden faces are (re)drawn at the end
+ fi;
+ else:
+ if vec_dprod_(normal_vec,v_c)<0:%visible
+ if not drawhidden:drawcontour(p,contour_width,contour_color)();fi;
+ else: % hidden
+ if drawhidden:
+ drawcontour(p,contour_width,contour_color)(dashed evenly);
+ fi;
+ fi;
+ fi;
+ free_vec(v_c);free_vec(v_b);free_vec(v_a);free_vec(normal_vec);
+ fi;
+enddef;
+
+% |p| is the path to draw (a face contour), |thickness| is the pen width
+% |col| is the color and |type| is a line modifier.
+def drawcontour(expr p,thickness,col)(text type)=
+ if draw_contours and (thickness>0):
+ pickup pencircle scaled thickness;
+ draw p withcolor background; % avoid strange overlapping dashes
+ draw p type withcolor col;
+ pickup pencircle scaled .4pt;
+ fi;
+enddef;
+
+%%\newpage
+% Variables for face handling. First, we have an array for lists of vertices
+% corresponding to faces.
+string face_points_[];% analogous to |vec| arrays
+
+% Then, we have an array of colors. A color needs to be a string
+% representing an hexadecimal RGB coding of a color.
+string face_color_[];
+
+% |name| is the name of an object instance
+vardef draw_faces(expr name)=
+ save tmpdef;string tmpdef;
+ define_current_face_offset_(name);
+ % first the hidden faces (dashes must be drawn first):
+ for i:=1 upto obj_faces_(name):
+ tmpdef:="draw_face(" & face_points_[face(i)]
+ & ")(hexcolor(" & ditto & face_color_[face(i)] & ditto
+ & "),true)";scantokens tmpdef;
+ endfor;
+ % then, the visible faces:
+ for i:=1 upto obj_faces_(name):
+ tmpdef:="draw_face(" & face_points_[face(i)]
+ & ")(hexcolor(" & ditto & face_color_[face(i)] & ditto
+ & "),false)";scantokens tmpdef;
+ endfor;
+enddef;
+
+% Draw point |n| of object instance |name|
+vardef draw_point(expr name,n)=
+ define_current_point_offset_(name);
+ project_point(ipnt_(n),pnt(n));
+ if z[ipnt_(n)] <> (too_big_,too_big_):
+ pickup pencircle scaled 5pt;
+ drawdot(z[ipnt_(n)]);
+ pickup pencircle scaled .4pt;
+ fi;
+enddef;
+
+vardef draw_axes(expr r,g,b)=
+ project_point(1,vec_null);
+ project_point(2,vec_I);
+ project_point(3,vec_J);
+ project_point(4,vec_K);
+ if (z1<>(too_big_,too_big_)):
+ if (z2<>(too_big_,too_big_)):
+ drawarrow z1--z2 dashed evenly withcolor r;
+ fi;
+ if (z3<>(too_big_,too_big_)):
+ drawarrow z1--z3 dashed evenly withcolor g;
+ fi;
+ if (z4<>(too_big_,too_big_)):
+ drawarrow z1--z4 dashed evenly withcolor b;
+ fi;
+ fi;
+enddef;
+
+% Draw a polygonal line through the list of points
+% This implementation does not work if you call
+% |draw_lines(i,i+4)| because \MP{} adds parentheses around
+% the value of |i|.
+def draw_lines(text vertices)=
+ begingroup % so that we can |let| |draw_lines|
+ save j,num,np;
+ % first, we copy all the indexes in an array, so that
+ % it is easier to go through them
+ j=1;
+ for $=vertices:num[j]=$;j:=j+1;endfor;
+ np=j-1;
+ for j:=1 upto np-1:
+ draw z[ipnt_(num[j])]--z[ipnt_(num[j+1])];
+ endfor;
+ endgroup
+enddef;
+
+let draw_line=draw_lines;
+
+% Draw an arrow between points |i| and |j| of current object
+% This is used from the |draw| definition of an object.
+def draw_arrow(expr i,j)=
+ drawarrow z[ipnt_(i)]--z[ipnt_(j)];
+enddef;
+
+% Draw a line between points |i| of object |obja| and |j| of |objb|
+% This is used when outside an object (i.e., we can't presuppose
+% any object offset)
+vardef draw_line_inter(expr obja, i, objb, j)=
+ project_point(1,pnt_obj(obja,i));
+ project_point(2,pnt_obj(objb,j));
+ draw z1--z2;
+enddef;
+
+% Draw an arrow between points |i| of object |obja| and |j| of |objb|
+% This is used when outside an object (i.e., we can't presuppose
+% any object offset)
+vardef draw_arrow_inter(expr obja, i, objb, j)=
+ project_point(1,pnt_obj(obja,i));
+ project_point(2,pnt_obj(objb,j));
+ draw z1--z2;
+enddef;
+
+%%\newpage
+% Definition of a macro |obj_name| returning an object name
+% when given an absolute
+% face number. This definition is built incrementally through a string,
+% everytime a new object is defined.
+% |obj_name| is defined by |redefine_obj_name_|.
+
+% Initial definition
+string index_to_name_;
+index_to_name_="def obj_name(expr i)=if i<1:";
+
+% |name| is the name of an object instance
+% |n| is the absolute index of its last face
+def redefine_obj_name_(expr name,n)=
+ index_to_name_:=index_to_name_ & "elseif i<=" & decimal n & ":" & ditto
+ & name & ditto;
+ scantokens begingroup index_to_name_ & "fi;enddef;" endgroup;
+enddef;
+
+% |i| is an absolute face number
+% |vertices| is a string representing a list of vertices
+% |rgbcolor| is a string representing a color in rgb hexadecimal
+def set_face(expr i,vertices,rgbcolor)=
+ face_points_[i]:=vertices;face_color_[i]:=rgbcolor;
+enddef;
+
+% |i| is a local face number
+% |vertices| is a string representing a list of vertices
+% |rgbcolor| is a string representing a color in rgb hexadecimal
+def set_obj_face(expr i,vertices,rgbcolor)=set_face(face(i),vertices,rgbcolor)
+enddef;
+
+% |i| is a local face number of object |inst|
+% |rgbcolor| is a string representing a color in rgb hexadecimal
+def set_obj_face_color(expr inst,i,rgbcolor)=
+ face_color_[face_obj(inst,i)]:=rgbcolor;
+enddef;
+
+
+%%\newpage
+%%\title{Compute the vectors corresponding to the observer's viewpoint}
+% (vectors |ObsI_|,|ObsJ_| and |ObsK_| in the |vec_I|,|vec_J|,
+% |vec_K| reference; and vectors |IObsI_|,|IObsJ_| and |IObsK_|
+% which are |vec_I|,|vec_J|,|vec_K|
+% in the |ObsI_|,|ObsJ_|,|ObsK_| reference)
+%%\figure{vect-fig.16}
+%% (here, $\psi>0$, $\theta<0$ and $\phi>0$; moreover,
+%% $\vert\theta\vert \leq 90^\circ$)
+
+def compute_reference(expr psi,theta,phi)=
+ % |ObsI_| defines the direction of observation;
+ % |ObsJ_| and |ObsK_| the orientation
+ % (but one of these two vectors is enough,
+ % since |ObsK_| = |ObsI_| $\land$ |ObsJ_|)
+ % The vectors are found by rotations of |vec_I|,|vec_J|,|vec_K|.
+ vec_def_vec_(ObsI_,vec_I);vec_def_vec_(ObsJ_,vec_J);
+ vec_def_vec_(ObsK_,vec_K);
+ vec_rotate_(ObsI_,ObsK_,psi);
+ vec_rotate_(ObsJ_,ObsK_,psi);% gives ($u$,$v$,$z$)
+ vec_rotate_(ObsI_,ObsJ_,theta);
+ vec_rotate_(ObsK_,ObsJ_,theta);% gives ($Obs_x$,$v$,$w$)
+ vec_rotate_(ObsJ_,ObsI_,phi);
+ vec_rotate_(ObsK_,ObsI_,phi);% gives ($Obs_x$,$Obs_y$,$Obs_z$)
+ % The passage matrix $P$ from |vec_I|,|vec_J|,|vec_K|
+ % to |ObsI_|,|ObsJ_|,|ObsK_| is the matrix
+ % composed of the vectors |ObsI_|,|ObsJ_| and |ObsK_| expressed
+ % in the base |vec_I|,|vec_J|,|vec_K|.
+ % We have $X=P X'$ where $X$ are the coordinates of a point
+ % in |vec_I|,|vec_J|,|vec_K|
+ % and $X'$ the coordinates of the same point in |ObsI_|,|ObsJ_|,|ObsK_|.
+ % In order to get $P^{-1}$, it suffices to build vectors using
+ % the previous rotations in the inverse order.
+ vec_def_vec_(IObsI_,vec_I);vec_def_vec_(IObsJ_,vec_J);
+ vec_def_vec_(IObsK_,vec_K);
+ vec_rotate_(IObsK_,IObsI_,-phi);vec_rotate_(IObsJ_,IObsI_,-phi);
+ vec_rotate_(IObsK_,IObsJ_,-theta);vec_rotate_(IObsI_,IObsJ_,-theta);
+ vec_rotate_(IObsJ_,IObsK_,-psi);vec_rotate_(IObsI_,IObsK_,-psi);
+enddef;
+
+%%\newpage
+%%\title{Point of view}
+% This macro computes the three angles necessary for |compute_reference|
+% |name| = name of an instance of an object
+% |target| = target point (local to object |name|)
+% |phi| = angle
+vardef point_of_view_obj(expr name,target,phi)=
+ define_current_point_offset_(name);% enables |pnt|
+ point_of_view_abs(pnt(target),phi);
+enddef;
+
+% Compute absolute perspective. |target| is an absolute point number
+% |phi| = angle
+% This function also computes two vectors needed in case
+% of an oblique projection.
+vardef point_of_view_abs(expr target,phi)=
+ save psi,theta;
+ new_vec(v_a);
+ vec_diff_(v_a,target,Obs);
+ vec_mult_(v_a,v_a,1/vec_mod_(v_a));
+ psi=angle((vec[v_a]x,vec[v_a]y));
+ theta=-angle((vec[v_a]x++vec[v_a]y,vec[v_a]z));
+ compute_reference(psi,theta,phi);
+ if projection_type=2: % oblique
+ % we start by checking that at a minimum the three points defining
+ % the projection plane have different indexes; it doesn't mean
+ % the plane if well defined, but if two values are identical,
+ % the plane can't be well defined.
+ if ((projection_plane1<>projection_plane2) and
+ (projection_plane1<>projection_plane3) and
+ (projection_plane2<>projection_plane3)):
+ new_line_(l)(Obs,Obs);
+ vec_sum_(l2,ObsI_,Obs);
+ if def_inter_p_l_pl_(ObliqueCenter_)(l)(projection_plane):
+ project_vectors(ProjK_,ProjJ_)(ObsK_,ObsJ_)(l)(projection_plane);
+ % define the projection direction
+ set_line_(projection_direction)(Obs,ObliqueCenter_);
+ else:
+ message "Anomalous oblique projection:";
+ message " the observer is watching parallely to the plane";
+ fi;
+ free_line(l);
+ else:
+ message "Anomalous projection plane; did you define it?";
+ fi;
+ fi;
+ free_vec(v_a);
+enddef;
+
+
+% Distance between the observer and point |n| of object |name|
+% Result is put in |dist|
+vardef obs_distance(text dist)(expr name,n)=
+ new_vec(v_a);
+ define_current_point_offset_(name);% enables |pnt|
+ dist:=vec_mod_(v_a,pnt(n),Obs);
+ free_vec(v_a);
+enddef;
+
+%%\newpage
+%%\title{Vector and point allocation}
+% Allocation is done through a stack of vectors
+numeric last_vec_;
+last_vec_=0;
+
+% vector allocation
+% (this must not be a |vardef| because the vector |v| saved is not saved
+% in this macro, but in the calling context)
+def new_vec(text v)=
+ save v;
+ new_vec_(v);
+enddef;
+
+def new_vec_(text v)=
+ v:=incr(last_vec_);
+ %|message "Vector " & decimal (last_vec_+1) & " allocated";|
+enddef;
+
+let new_point = new_vec;
+let new_point_ = new_vec_;
+
+def new_points(text p)(expr n)=
+ save p;
+ numeric p[];
+ for i:=1 upto n:new_point_(p[i]);endfor;
+enddef;
+
+% Free a vector
+% A vector can only be freed safely when it was the last vector created.
+def free_vec(expr i)=
+ if i=last_vec_: last_vec_:=last_vec_-1;
+ %|message "Vector " & decimal i & " freed";|
+ else: errmessage("Vector " & decimal i & " can't be freed!");
+ fi;
+enddef;
+
+let free_point = free_vec;
+
+def free_points(text p)(expr n)=
+ for i:=n step-1 until 1:free_point(p[i]);endfor;
+enddef;
+
+%%\title{Debugging}
+
+def show_vec(expr t,i)=
+ message "Vector " & t & "="
+ & "(" & decimal vec[i]x & "," & decimal vec[i]y & ","
+ & decimal vec[i]z & ")";
+enddef;
+
+% One can write |show_point("2",pnt_obj("obj",2));|
+let show_point=show_vec;
+
+def show_pair(expr t,zz)=
+ message t & "=(" & decimal xpart(zz) & "," & decimal ypart(zz) & ")";
+enddef;
+
+%%\newpage
+%%\title{Access to object features}
+% |a| must be a string representing a class name, such as |"dodecahedron"|.
+% |b| is the tail of a macro name.
+
+def obj_(expr a,b,i)=
+ scantokens
+ begingroup save n;string n;n=a & b & i;n
+ endgroup
+enddef;
+
+def obj_points_(expr name)=
+ obj_(obj_class_(name),"_points",name)
+enddef;
+
+def obj_faces_(expr name)=
+ obj_(obj_class_(name),"_faces",name)
+enddef;
+
+vardef obj_point_offset_(expr name)=
+ obj_(obj_class_(name),"_point_offset",name)
+enddef;
+
+vardef obj_face_offset_(expr name)=
+ obj_(obj_class_(name),"_face_offset",name)
+enddef;
+
+def obj_class_(expr name)=obj_(name,"_class","") enddef;
+
+%%\newpage
+def define_point_offset_(expr name,o)=
+ begingroup save n,tmpdef;
+ string n,tmpdef;
+ n=obj_class_(name) & "_point_offset" & name;
+ expandafter numeric scantokens n;
+ scantokens n:=last_point_offset_;
+ last_point_offset_:=last_point_offset_+o;
+ tmpdef="def " & obj_class_(name) & "_points" & name &
+ "=" & decimal o & " enddef";
+ scantokens tmpdef;
+ endgroup
+enddef;
+
+def define_face_offset_(expr name,o)=
+ begingroup save n,tmpdef;
+ string n,tmpdef;
+ n=obj_class_(name) & "_face_offset" & name;
+ expandafter numeric scantokens n;
+ scantokens n:=last_face_offset_;
+ last_face_offset_:=last_face_offset_+o;
+ tmpdef="def " & obj_class_(name) & "_faces" & name &
+ "=" & decimal o & " enddef";
+ scantokens tmpdef;
+ endgroup
+enddef;
+
+def define_current_point_offset_(expr name)=
+ save current_point_offset_;
+ numeric current_point_offset_;
+ current_point_offset_:=obj_point_offset_(name);
+enddef;
+
+def define_current_face_offset_(expr name)=
+ save current_face_offset_;
+ numeric current_face_offset_;
+ current_face_offset_:=obj_face_offset_(name);
+enddef;
+
+
+%%\newpage
+%%\title{Drawing an object}
+% |name| is an object instance
+vardef draw_obj(expr name)=
+ save tmpdef;
+ string tmpdef;
+ current_obj:=name;
+ tmpdef="draw_" & obj_class_(name);
+ project_obj(name);% compute screen coordinates
+ save overflow; boolean overflow; overflow=false;
+ for $:=1 upto obj_points_(name):
+ if z[ipnt_($)]=(too_big_,too_big_):overflow:=true;
+ x[ipnt_($)] := 10; % so that the figure can be drawn anyway
+ y[ipnt_($)] := 10;
+ % why can't I write z[ipnt_($)]:=(10,10); ?
+ fi;
+ exitif overflow;
+ endfor;
+ if overflow:
+ message "Figure has overflows";
+ message " (at least one point is not visible ";
+ message " and had to be drawn at a wrong place)";
+ fi;
+ scantokens tmpdef(name);
+enddef;
+
+%%\title{Normalization of an object}
+% This macro translates an object so that a list of vertices is centered
+% on the origin, and the last vertex is put on a sphere whose radius is 1.
+% |name| is the name of the object and |vertices| is a list
+% of points whose barycenter will define the center of the object.
+% (|vertices| need not be the list of all vertices)
+vardef normalize_obj(expr name)(text vertices)=
+ save nvertices,last;
+ nvertices=0;
+ new_vec(v_a);vec_def_(v_a,0,0,0)
+ forsuffixes $=vertices:
+ vec_sum_(v_a,v_a,pnt($));
+ nvertices:=nvertices+1;
+ last:=$;
+ endfor;
+ vec_mult_(v_a,v_a,-1/nvertices);
+ translate_obj(name,v_a);% object centered on the origin
+ scale_obj(name,1/vec_mod(last));
+ free_vec(v_a);
+enddef;
+
+
+%%\newpage
+%%\title{General definitions}
+% Vector arrays
+numeric vec[]x,vec[]y,vec[]z;
+
+% Reference vectors $\vec{0}$, $\vec{\imath}$, $\vec{\jmath}$ and $\vec{k}$
+% and their definition
+new_vec(vec_null);new_vec(vec_I);new_vec(vec_J);new_vec(vec_K);
+vec_def_(vec_null,0,0,0);
+vec_def_(vec_I,1,0,0);vec_def_(vec_J,0,1,0);vec_def_(vec_K,0,0,1);
+numeric point_null;
+point_null=vec_null;
+
+% Observer
+new_point(Obs);
+% default value:
+set_point_(Obs,0,0,20);
+
+% Observer's vectors
+new_vec(ObsI_);new_vec(ObsJ_);new_vec(ObsK_);
+% default values:
+vec_def_vec_(ObsI_,vec_I);
+vec_def_vec_(ObsJ_,vec_J);
+vec_def_vec_(ObsK_,vec_K);
+
+new_vec(IObsI_);new_vec(IObsJ_);new_vec(IObsK_);
+
+% These vectors will be vectors of the projection plane,
+% in case of oblique projections:
+new_vec(ProjK_);new_vec(ProjJ_); % there is no |ProjI_|
+
+% This will be the center of the projection plane, in oblique projections
+new_point(ObliqueCenter_);
+
+
+% distance observer/plane (must be $>0$)
+numeric Obs_dist; % represents |Obs_dist| $\times$ |drawing_scale|
+% default value:
+Obs_dist=2; % means |Obs_dist| $\times$ |drawing_scale|
+
+% current object being drawn
+string current_obj;
+
+% kind of projection: 0 for linear (or central) perspective, 1 for parallel,
+% 2 for oblique projection
+% (default is 0)
+numeric projection_type;
+projection_type:=0;
+
+% Definition of a projection plane (only used in oblique projections)
+%
+new_plane_(projection_plane)(1,1,1); % the initial value is irrelevant
+
+% Definition of a projection direction (only used in oblique projections)
+new_line_(projection_direction)(1,1); % the initial value is irrelevant
+
+% this positions the observer at vector |p| (the point observed)
+% + |d| (distance) * (k-(i+j))
+def isometric_projection(expr i,j,k,p,d,phi)=
+ trimetric_projection(i,j,k,1,1,1,p,d,phi);
+enddef;
+
+% this positions the observer at vector |p| (the point observed)
+% + |d| (distance) * (ak-(i+j))
+def dimetric_projection(expr i,j,k,a,p,d,phi)=
+ trimetric_projection(i,j,k,1,1,a,p,d,phi);
+enddef;
+
+% this positions the observer at vector |p| (the point observed)
+% + |d| (distance) * (k-(i+j))
+% |a|, |b| and |c| are multiplicative factors to vectors |i|, |j| and |k|
+vardef trimetric_projection(expr i,j,k,a,b,c,p,d,phi)=
+ save v_a,v_b,v_c;
+ new_vec(v_a);new_vec(v_b);new_vec(v_c);
+ vec_mult_(v_a,i,a);vec_mult_(v_b,j,b);vec_mult_(v_c,k,c);
+ vec_sum_(Obs,v_a,v_b);
+ vec_diff_(Obs,v_c,Obs);
+ vec_mult_(Obs,Obs,d);
+ vec_sum_(Obs,Obs,p);
+ point_of_view_abs(p,phi);
+ projection_type:=1;
+ free_vec(v_c);free_vec(v_b);free_vec(v_a);
+enddef;
+
+% |hor| is an horizontal plane (in the sense that it will represent
+% the horizontal for the observer)
+% |p| is the point in space that the observer targets (center of screen)
+% |a| is an angle (45 degrees corresponds to cavalier drawing)
+% |b| is an angle (see examples defined below)
+% |d| is the distance of the observer
+vardef oblique_projection(text hor)(expr p,a,b,d)=
+ save _l,v_a,v_b,v_c,xxx_,obsJangle_;
+ new_vec(v_a);new_vec(v_b);new_vec(v_c);
+ % we first compute a horizontal line:
+ new_line_(_l)(1,1);
+ if def_inter_l_pl_pl(_l)(hor)(projection_plane):
+ vec_diff_(v_a,_l2,_l1); % horizontal vector
+ % then, we find a normal to the projection plane:
+ def_normal_p_(v_b)(projection_plane);
+ % complete the line and the vector by a third vector (=vertical)
+ vec_prod_(v_c,v_a,v_b);
+ % we make |v_a| a copy of |v_b| since we no longer need |v_b|
+ vec_def_vec_(v_a,v_b);
+ % we rotate |v_b| by an angle |a| around |v_c|
+ vec_rotate_(v_b,v_c,a);
+ % we rotate |v_b| by an angle |b| around |v_a|
+ vec_rotate_(v_b,v_a,b);
+ % we put the observer at the distance |d| of |p| in
+ % the direction of |v_b|:
+ vec_unit_(v_b,v_b);
+ vec_mult_(v_b,v_b,d);vec_sum_(Obs,p,v_b);
+ % We now have to make sure that point |p| and point |Obs|
+ % are on different sides of the projection plane. For this,
+ % we compute two dot products:
+ new_vec(v_d);new_vec(v_e);
+ vec_diff_(v_d,p,_l1);vec_diff_(v_e,Obs,_l1);
+ if vec_dprod_(v_d,v_a)*vec_dprod_(v_e,v_a)>=0:
+ % |p| and |Obs| are on the same side of the projection plane
+ % |Obs| needs to be recomputed.
+ vec_mult_(v_b,v_b,-1);
+ vec_sum_(Obs,p,v_b);
+ fi;
+ free_vec(v_e);free_vec(v_d);
+ projection_type:=2; % needs to be set before |point_of_view_abs|
+ point_of_view_abs(p,90); % this computes |ObliqueCenter_|
+ % and now, make sure the vectors defining the observer are right:
+ % Create the plane containing lines _l and projection_direction
+ % (defined by point_of_view_abs):
+ new_plane_(xxx_)(1,1,1);
+ def_plane_pl_l_l(xxx_)(_l)(projection_direction);
+ % Compute the angle of |ObsK_| with this plane:
+ obsJangle_=vangle_v_pl_(ObsK_)(xxx_);
+ % rotate |ObsJ_| and |ObsK_| by |obsJangle_| around |ObsI_|
+ vec_rotate_(ObsJ_,ObsI_,obsJangle_);
+ vec_rotate_(ObsK_,ObsI_,obsJangle_);
+ if abs(vangle_v_pl_(ObsK_)(xxx_))>1: % the rotation was done
+ % in the wrong direction
+ vec_rotate_(ObsJ_,ObsI_,-2obsJangle_);
+ vec_rotate_(ObsK_,ObsI_,-2obsJangle_);
+ fi;
+ % |vec_rotate_(ObsJ_,ObsI_,45);| % planometric test
+ % |vec_rotate_(ObsK_,ObsI_,45);| % planometric test
+ free_plane(xxx_);
+ % and now, |ProjJ_| and |ProjK_| must be recomputed:
+ project_vectors(ProjK_,ProjJ_)(ObsK_,ObsJ_)%
+ (projection_direction)(projection_plane);
+ else:
+ message "Error: the ``horizontal plane'' cannot be";
+ message " parallel to the projection plane.";
+ fi;
+ free_line(_l);
+ free_vec(v_c);free_vec(v_b);free_vec(v_a);
+enddef;
+
+% These two are the most common values for the third parameter
+% of |oblique_projection|
+numeric CAVALIER;CAVALIER=45;
+numeric CABINET;CABINET=angle((1,.5)); % atn(.5)
+
+% Screen Size
+% The screen size is defined through two angles: the horizontal field
+% and the vertical field
+numeric h_field,v_field;
+h_field=100; % degrees
+v_field=70; % degrees
+
+% Observer's orientation, defined by three angles
+numeric Obs_psi,Obs_theta,Obs_phi;
+% default value:
+Obs_psi=0;Obs_theta=90;Obs_phi=0;
+
+% This array relates an absolute object point number to the
+% absolute point number (that is, to the |vec| array).
+% The absolute object point number is the rank of a point
+% with respect to all object points. The absolute point number
+% considers in addition the extra points, such as |Obs|, which do
+% not belong to an object.
+% If |i| is an absolute object point number, |points_[i]|
+% is the absolute point number.
+numeric points_[];
+
+% |name| is the name of an object instance
+% |npoints| is its number of defining points
+def new_obj_points(expr name,npoints)=
+ define_point_offset_(name,npoints);define_current_point_offset_(name);
+ for i:=1 upto obj_points_(name):new_point_(pnt(i));endfor;
+enddef;
+
+% |name| is the name of an object instance
+% |nfaces| is its number of defining faces
+def new_obj_faces(expr name,nfaces)=
+ define_face_offset_(name,nfaces);define_current_face_offset_(name);
+ redefine_obj_name_(name,current_face_offset_+nfaces);
+enddef;
+
+%%\newpage
+% Absolute point number corresponding to object point number |i|
+% This macro must only be used within the function defining an object
+% (such as |def_cube|) or the function drawing an object (such as
+% |draw_cube|).
+def ipnt_(expr i)=i+current_point_offset_ enddef;
+def pnt(expr i)=points_[ipnt_(i)] enddef;
+
+def face(expr i)=(i+current_face_offset_) enddef;
+
+% Absolute point number corresponding to local point |n|
+% in object instance |name|
+vardef pnt_obj(expr name,n)=
+ points_[n+obj_point_offset_(name)]
+ %hide(define_current_point_offset_(name);) pnt(n) % HAS SIDE EFFECTS
+enddef;
+
+% Absolute face number corresponding to local face |n|
+% in object instance |name|
+vardef face_obj(expr name,n)=
+ (n+obj_face_offset_(name))
+ %hide(define_current_face_offset_(name);) face(n) % HAS SIDE EFFECTS
+enddef;
+
+
+% Scale
+numeric drawing_scale;
+drawing_scale=2cm;
+
+% Color
+% This function is useful when a color is expressed in hexadecimal.
+% This does the opposite from |tohexcolor|
+def hexcolor(expr s)=
+ (hex(substring (0,2) of s)/255,hex(substring (2,4) of s)/255,
+ hex(substring (4,6) of s)/255)
+enddef;
+
+% Convert a color triple into a hexadecimal color string.
+% |rv|, |gv| and |bv| are values between 0 and 1.
+% This does the opposite from |hexcolor|
+vardef tohexcolor(expr rv,gv,bv)=
+ save dig;numeric dig[];
+ hide(
+ dig2=floor(rv*255);dig1=floor((dig2)/16);dig2:=dig2-16*dig1;
+ dig4=floor(gv*255);dig3=floor((dig4)/16);dig4:=dig4-16*dig3;
+ dig6=floor(bv*255);dig5=floor((dig6)/16);dig6:=dig6-16*dig5;
+ for i:=1 upto 6:
+ if dig[i]<10:dig[i]:=dig[i]+48;
+ else:dig[i]:=dig[i]+87;
+ fi;
+ endfor;
+ )
+ char(dig1)&char(dig2)&char(dig3)&char(dig4)&char(dig5)&char(dig6)
+enddef;
+
+% Conversions
+
+% Returns a string encoding the integer |n| as follows:
+% if $n=10*a+b$ with $b<10$,
+% |alphabetize|(|n|)=|alphabetize|(|a|) |&| |char (65+b)|
+% For instance, alphabetize(3835) returns "DIDF"
+% This function is useful in places where digits are not allowed.
+def alphabetize(expr n)=
+ if (n>9):
+ alphabetize(floor(n/10)) & fi
+ char(65+n-10*floor(n/10))
+enddef;
+
+% Filling and contours
+boolean filled_faces,draw_contours;
+filled_faces=true;
+draw_contours=true;
+numeric contour_width; % thickness of contours
+contour_width=1pt;
+color contour_color; % face contours
+contour_color=black;
+
+% Overflow control
+% An overflow can occur when an object is too close from the observer
+% or if an object is out of sight. We use a special value to mark
+% coordinates which would lead to an overflow.
+numeric too_big_;
+too_big_=4000;
+
+
+% Object offset (the points defining an object are arranged
+% in a single array, and the objects are easier to manipulate
+% if the point numbers are divided into a number and an offset).
+numeric last_point_offset_,last_face_offset_;
+last_point_offset_=0;last_face_offset_=0;
+
+endinput