1 /*
2  * $Id: slice3.i,v 1.1 2005-09-18 22:06:09 dhmunro Exp $
3  * find 2D slices of a 3D hexahedral mesh
4  */
5 /* Copyright (c) 2005, The Regents of the University of California.
6  * All rights reserved.
7  * This file is part of yorick (http://yorick.sourceforge.net).
8  * Read the accompanying LICENSE file for details.
9  */
10 
11 /*
12  * Caveats:
13  * (A) Performance is reasonably good, but may still be a factor of
14  *     several slower than what could be achieved in compiled code.
15  * (B) Only a simple in-memory mesh model is implemented here.
16  *     However, hooks are supplied for more interesting possibilities,
17  *     such as a large binary file resident mesh data base.
18  * (C) There is a conceptual difficulty with _walk3 for the case
19  *     of a quad face all four of whose edges are cut by the slicing
20  *     plane.  This can only happen when two opposite corners are
21  *     above and the other two below the slicing plane.  There are
22  *     three possible ways to connect the four intersection points in
23  *     two pairs: (1) // (2) \\ and (3) X  There is a severe problem
24  *     with (1) and (2) in that a consistent decision must be made
25  *     when connecting the points on the two cells which share the
26  *     face - that is, each face must carry information on which way
27  *     it is triangulated.  For a regular 3D mesh, it is relatively
28  *     easy to come up with a consistent scheme for triangulating faces,
29  *     but for a general unstructured mesh, each face itself must carry
30  *     this information.  This presents a huge challenge for data flow,
31  *     which I don't believe is worthwhile.  Because the X choice is
32  *     unique, and I don't see why we shouldn't use it here.
33  *     For contouring routines, we reject the X choice on esthetic
34  *     grounds, and perhaps that will prove to be the case here as
35  *     well - but I believe we should try the simple way out first.
36  *     In this case, we are going to be filling these polygons with
37  *     a color representing a function value in the cell.  Since the
38  *     adjacent cells should have nearly the same values, the X-traced
39  *     polygons will have nearly the same color, and I doubt there will
40  *     be an esthetic problem.  Anyway, the slice3 implemented
41  *     below produces the unique X (bowtied) polygons, rather than
42  *     attempting to choose between // or \\ (non-bowtied) alternatives.
43  *     Besides, in the case of contours, the trivial alternating
44  *     triangulation scheme is just as bad esthetically as every
45  *     zone triangulated the same way!
46  */
47 
48 /* ------------------------------------------------------------------------ */
49 
plane3(normal,point)50 func plane3(normal, point)
51 /* DOCUMENT plane3(normal, point)
52          or plane3([nx,ny,nz], [px,py,pz])
53 
54      returns [nx,ny,nz,pp] for the specified plane.
55 
56    SEE ALSO: slice3, mesh3
57  */
58 {
59   /* the normal doesn't really need to be normalized, but this
60    * has the desirable side effect of blowing up if normal==0 */
61   normal/= abs(normal(1),normal(2),normal(3));
62   return grow(normal,normal(+)*point(+));
63 }
64 
65 /* ------------------------------------------------------------------------ */
66 
mesh3(x,y,z,..)67 func mesh3(x,y,z,..)
68 /* DOCUMENT mesh3(x,y,z)
69          or mesh3(x,y,z, f1,f2,...)
70          or mesh3(xyz, f1,f2,...)
71          or mesh3(nxnynz, dxdydz, x0y0z0, f1,f2,...)
72 
73      make mesh3 argument for slice3, xyz3, getv3, etc., functions.
74      X, Y, and Z are each 3D coordinate arrays.  The optional F1, F2,
75      etc. are 3D arrays of function values (e.g. density, temperature)
76      which have one less value along each dimension than the coordinate
77      arrays.  The "index" of each zone in the returned mesh3 is
78      the index in these cell-centered Fi arrays, so every index from
79      one through the total number of cells indicates one real cell.
80      The Fi arrays can also have the same dimensions as X, Y, or Z
81      in order to represent point-centered quantities.
82 
83      If X has four dimensions and the length of the first is 3, then
84      it is interpreted as XYZ (which is the quantity actually stored
85      in the returned cell list).
86 
87      If X is a vector of 3 integers, it is interpreted as [nx,ny,nz]
88      of a uniform 3D mesh, and the second and third arguments are
89      [dx,dy,dz] and [x0,y0,z0] respectively.  (DXDYDZ represent the
90      size of the entire mesh, not the size of one cell, and NXNYNZ are
91      the number of cells, not the number of points.)
92 
93    SEE ALSO: slice3, xyz3, getv3, getc3
94  */
95 {
96   /* other sorts of meshes are possible -- a mesh which lives
97    * in a binary file is an obvious example -- which would need
98    * different workers for xyz3, getv3, getc3, and iterator3
99    * iterator3_rect may be more general than the other three;
100    * as long as the cell dimensions are the car of the list
101    * which is the 2nd car of m3, it will work */
102   virtuals= _lst(xyz3_rect, getv3_rect, getc3_rect, iterator3_rect);
103 
104   dims= dimsof(x);
105   if (dims(1)==4 && dims(2)==3 && min(dims)>=2) {
106     xyz= &double(x);
107     n= 2*(!is_void(y))+(!is_void(z));
108     dims= grow([3], dims(3:5));
109   } else if (dims(1)==1 && dims(2)==3 && structof(0+x)==long && min(x)>0 &&
110              dimsof(y)(1)==1 && dimsof(z)(1)==1 &&
111              numberof(y)==3 && numberof(z)==3) {
112     xyz= &double([y/double(x), z]);
113     dims= grow([3], 1+x);
114     n= 0;
115     _car, virtuals, 1, xyz3_unif;
116   } else {
117     if (dims(1)!=3 || min(dims)<2 ||
118         dimsof(y)(1)!=3 || anyof(dimsof(y)!=dims) ||
119         dimsof(z)(1)!=3 || anyof(dimsof(z)!=dims))
120       error, "X,Y,Z are not viable 3D coordinate mesh arrays";
121     xyz= array(0.0, 3, dimsof(x));
122     xyz(1,..)= x;
123     xyz(2,..)= y;
124     xyz(3,..)= z;
125     xyz= &xyz;
126     n= 0;
127   }
128 
129   dim_cell= dims;
130   dim_cell(2:4)-= 1;
131   m3= _lst(virtuals, _lst(dim_cell(2:4), *xyz));
132   last= _cdr(m3);
133 
134   while (n || more_args()) {
135     if (n) {
136       if (n&2) {
137         f= y;
138         n&= 1;
139       } else {
140         f= z;
141         n= 0;
142       }
143     } else {
144       f= next_arg();
145     }
146     if (dimsof(f)(1)!=3 ||
147         (anyof(dimsof(f)!=dims) && anyof(dimsof(f)!=dim_cell))) {
148       error, "F"+pr1(i)+" is not a viable 3D cell value";
149     }
150     _cdr, last, 1, _lst(f);
151     last= _cdr(last);
152   }
153 
154   return m3;
155 }
156 
157 /* ------------------------------------------------------------------------ */
158 
159 /* Ways that a list of polygons can be extracted:
160  * Basic idea:
161  *   (1) At each *vertex* of the cell list, a function value is defined.
162  *       This is the "slicing function", perhaps the equation of a plane,
163  *       perhaps some other vertex-centered function.
164  *   (2) The slice3 routine returns a list of cells for which the
165  *       function value changes sign -- that is, cells for which some
166  *       vertices have positive function values, and some negative.
167  *       The function values and vertex coordinates are also returned.
168  *   (3) The slice3 routine computes the points along the *edges*
169  *       of each cell where the function value is zero (assuming linear
170  *       variation along each edge).  These points will be vertices of
171  *       the polygons.  The routine also sorts the vertices into cyclic
172  *       order.
173  *   (4) A "color function" can be used to assign a "color" or other
174  *       value to each polygon.  If this function depends only on the
175  *       coordinates of the polygon vertices (e.g.- 3D lighting), then
176  *       the calculation can be done elsewhere.  There are two other
177  *       possibilities:  The color function might be a cell-centered
178  *       quantity, or a vertex-centered quantity (like the slicing
179  *       function) on the mesh.  In these cases, slice3 already
180  *       has done much of the work, since it "knows" cell indices,
181  *       edge interpolation coefficients, and the like.
182  *
183  * There are two particularly important cases:
184  * (1) Slicing function is a plane, coloring function is either a
185  *     vertex or cell centered mesh function.  Coloring function
186  *     might also be a *function* of one or more of the predefined
187  *     mesh functions.  If you're eventually going to sweep the whole
188  *     mesh, you want to precalculate it, otherwise on-the-fly might
189  *     be better.
190  * (2) Slicing function is a vertex centered mesh function,
191  *     coloring function is 3D shading (deferred).
192  *
193  * fslice(m3, vertex_list)
194  * vertex_list_iterator(m3, vertex_list, mesh3)
195  * fcolor(m3, vertex_list, fslice_1, fslice_2)
196  *   the coloring function may need the value of fslice at the vertices
197  *   in order to compute the color values by interpolation
198  * two "edge functions": one to detect edges where sign of fslice changes,
199  *   second to interpolate for fcolor
200  *
201  * slice3(m3, fslice, &nverts, &xyzverts, <fcolor>)
202  */
203 
204 func slice3(m3, fslice, &nverts, &xyzverts, fcolor, nointerp, value=)
205 /* DOCUMENT slice3, m3, fslice, nverts, xyzverts
206          or color_values= slice3(m3, fslice, nverts, xyzverts, fcolor)
207          or color_values= slice3(m3, fslice, nverts, xyzverts, fcolor, 1)
208 
209      slice the 3D mesh M3 using the slicing function FSLICE, returning
210      the lists NVERTS and XYZVERTS.  NVERTS is the number of vertices
211      in each polygon of the slice, and XYZVERTS is the 3-by-sum(NVERTS)
212      list of polygon vertices.  If the FCOLOR argument is present, the
213      values of that coloring function on the polygons are returned as
214      the value of the slice3 function (numberof(color_values) ==
215      numberof(NVERTS) == number of polygons).
216 
217      If the slice function FSLICE is a function, it should be of the
218      form:
219         func fslice(m3, chunk)
220      returning a list of function values on the specified chunk of the
221      mesh m3.  The format of chunk depends on the type of m3 mesh, so
222      you should use only the other mesh functions xyz3 and getv3 which
223      take m3 and chunk as arguments.  The return value of fslice should
224      have the same dimensions as the return value of getv3; the return
225      value of xyz3 has an additional first dimension of length 3.
226 
227      If FSLICE is a list of 4 numbers, it is taken as a slicing plane
228      with the equation FSLICE(+:1:3)*xyz(+)-FSLICE(4), as returned by
229      plane3.
230 
231      If FSLICE is a single integer, the slice will be an isosurface for
232      the FSLICEth variable associated with the mesh M3.  In this case,
233      the keyword value= must also be present, representing the value
234      of that variable on the isosurface.
235 
236      If FCOLOR is nil, slice3 returns nil.  If you want to color the
237      polygons in a manner that depends only on their vertex coordinates
238      (e.g.- by a 3D shading calculation), use this mode.
239 
240      If FCOLOR is a function, it should be of the form:
241         func fcolor(m3, cells, l, u, fsl, fsu, ihist)
242      returning a list of function values on the specified cells of the
243      mesh m3.  The cells argument will be the list of cell indices in
244      m3 at which values are to be returned.  l, u, fsl, fsu, and ihist
245      are interpolation coefficients which can be used to interpolate
246      from vertex centered values to the required cell centered values,
247      ignoring the cells argument.  See getc3 source code.
248      The return values should always have dimsof(cells).
249 
250      If FCOLOR is a single integer, the slice will be an isosurface for
251      the FCOLORth variable associated with the mesh M3.
252 
253      If the optional argument after FCOLOR is non-nil and non-zero,
254      then the FCOLOR function is called with only two arguments:
255         func fcolor(m3, cells)
256 
257    SEE ALSO: mesh3, plane3, xyz3, getv3, getc3, slice2, plfp
258  */
259 {
260   nverts= xyzverts= [];
261 
262   /* handle the different possibilities for fslice */
263   if (!is_func(fslice)) {
264     if (is_void(value) &&
265         dimsof(fslice)(1)==1 && numberof(fslice)==4) {
266       normal= fslice(1:3);
267       projection= fslice(4);
268       fslice= _plane_slicer;
269     } else if (dimsof(fslice)(1)==0 && structof(0+fslice)==long) {
270       if (is_void(value))
271         error, "value= keyword required when FSLICE is mesh variable";
272       iso_index= fslice;
273       fslice= _isosurface_slicer;
274     } else {
275       error, "illegal form of FSLICE argument, try help,slice3";
276     }
277   }
278 
279   /* will need cell list if fcolor function to be computed */
280   need_clist= !is_void(fcolor);
281 
282   /* test the different possibilities for fcolor */
283   if (need_clist && !is_func(fcolor)) {
284     if (dimsof(fcolor)(1)!=0 || structof(0+fcolor)!=long) {
285       error, "illegal form of FCOLOR argument, try help,slice3";
286     }
287   }
288 
289   /* chunk up the m3 mesh and evaluate the slicing function to
290    * find those cells cut by fslice==0
291    * chunking avoids potentially disasterously large temporaries
292    */
293   _xyz3_save= 1;      /* flag for xyz3 */
294   got_xyz= 0;
295   ntotal= nchunk= 0;
296   results= [];
297   for (chunk=iterator3(m3) ;
298        !is_void(chunk) ;
299        chunk=iterator3(m3,chunk)) {
300 
301     /* get the values of the slicing function at the vertices of
302      * this chunk
303      */
304     _xyz3= [];        /* if fslice calls xyz3, xyz saved here */
305     fs= fslice(m3, chunk);
306 
307     /* will need cell list if fslice did not compute xyz */
308     got_xyz= !is_void(_xyz3);
309     need_clist|= !got_xyz;
310 
311     /* If the m3 mesh is totally unstructured, the chunk should be
312      * arranged so that fslice returns an 2-by-2-by-2-by-ncells array
313      * of vertex values of the slicing function.
314      * On the other hand, if the mesh vertices are arranged in a
315      * rectangular grid (or a few patches of rectangular grids), the
316      * chunk should be the far less redundant rectangular patch.
317      */
318     dims= dimsof(fs);
319     irregular= numberof(dims)>4;
320     if (irregular) {
321       /* fs is a 2-by-2-by-2-by-ncells array
322        * here is the fastest way to generate the required cell list
323        */
324       clist= where((fs<0.0)(sum:1:8,1,1,) & 7);
325 
326     } else {
327       /* fs is an ni-by-nj-by-nk array
328        * result of the zcen is 0, 1/8, 2/8, ..., 7/8, or 1
329        */
330       clist= double(fs<0.0)(zcen,zcen,zcen);
331       clist= where(clist>.1 & clist<.9);
332       /* alternative possibilities which run about equally quickly are:
333        *    clist!=floor(clist)
334        *    clist%1.0
335        * these both rely on the fact that 0.5*(1.0+1.0)==1.0 exactly
336        * they also both call slow libm functions (floor and amod)
337        */
338     }
339 
340     if (numberof(clist)) {
341       ntotal+= numberof(clist);
342       /* we need to save:
343        * (1) the absolute cell indices of the cells in clist
344        * (2) the corresponding 2-by-2-by-2-by-ncells list of fslice
345        *     values at the vertices of these cells
346        */
347       if (irregular) {
348         fs= fs(,,,clist);
349         if (got_xyz) _xyz3= _xyz3(,,,,clist);
350       } else {
351         list= to_corners3(clist, dims(2), dims(3));
352         fs= fs(list);
353         if (got_xyz) _xyz3= _xyz3(,list);
354       }
355       /* here, the iterator converts to absolute cell indices without
356        * incrementing the chunk */
357       if (need_clist) clist= iterator3(m3, chunk, clist);
358       else clist= [];
359       if (!(nchunk&127)) grow, results, array(pointer, 3, nchunk+128);
360       ++nchunk;
361       results(1,nchunk)= &clist;
362       results(2,nchunk)= &fs;
363       results(3,nchunk)= &_xyz3;
364     }
365   }
366   _xyz3= [];  /* free memory */
367 
368   /* collect the results of the chunking loop */
369   if (!ntotal) return [];
370   if (need_clist) clist= array(0, ntotal);
371   fs= array(0.0, 2,2,2,ntotal);
372   if (got_xyz) xyz= array(0.0, 3, dimsof(fs));
373   for (i=1,k=0 ; i<=nchunk ; ++i) {
374     j= k+1;
375     k+= numberof(*results(1,i));
376     if (need_clist) clist(j:k)= *results(1,i);
377     fs(,,,j:k)= *results(2,i);
378     if (!is_void(xyz)) xyz(,,,,j:k)= *results(3,i);
379   }
380   results= [];  /* free memory */
381   if (!got_xyz) xyz= xyz3(m3, clist);
382 
383   /* produce the lists of edge intersection points
384    * -- generate 2x2x3x(nsliced) array of edge mask values
385    * (mask non-zero if edge is cut by plane) */
386   below= fs<0.0;
387   mask= array(0n, 2,2,3,ntotal);
388   mask(-,,,1,)= abs(below(dif,,,));
389   mask(,-,,2,)= abs(below(,dif,,));
390   mask(,,-,3,)= abs(below(,,dif,));
391   list= where(mask);
392   edges= list-1;
393   cells= edges/12;      /* 0-origin momentarily */
394   edges= (edges%12)+1;
395   /* construct edge endpoint indices in fs, xyz arrays
396    * the numbers are the endpoint indices corresponding to
397    * the order of the 12 edges in the mask array */
398   lower= [1,3,5,7, 1,2,5,6, 1,2,3,4](edges) + 8*cells;
399   upper= [2,4,6,8, 3,4,7,8, 5,6,7,8](edges) + 8*cells;
400   /* restore the cells array to 1-origin */
401   cells+= 1;
402 
403   /* interpolate to find edge intersection points */
404   fsl= fs(lower)(-,);
405   fsu= fs(upper)(-,);
406   /* following denominator guaranteed non-zero */
407   xyz= (xyz(,lower)*fsu-xyz(,upper)*fsl)/(fsu-fsl);
408 
409   /* The xyz array is now the output xyzverts array,
410    * but for the order of the points within each cell.  */
411 
412   /* give each sliced cell a "pattern index" between 0 and 255
413    * (non-inclusive) representing the pattern of its 8 corners
414    * above and below the slicing plane */
415   pattern= below(*,)(+,) * (1<<indgen(0:7))(+);
416   if (slice3_stats) {
417     extern poly_patterns;
418     poly_patterns= histogram(pattern, top=254);
419   }
420   /* broadcast the cell's pattern onto each of its sliced edges */
421   pattern= pattern(-:1:12,)(list);
422 
423   /* To each pattern, there corresponds a permutation of the
424    * twelve edges so that they occur in the order in which the
425    * edges are to be connected.  Let each such permuation be
426    * stored as a list of integers from 0 to 11 such that sorting
427    * the integers into increasing order rearranges the edges at
428    * the corresponding indices into the correct order.  (The position
429    * of unsliced edges in the list is arbitrary as long as the sliced
430    * edges are in the proper order relative to each other.)
431    * Let these permutations be stored in a 12-by-254 array
432    * poly_permutations (see next comment for explanation of 48): */
433   pattern= poly_permutations(12*(pattern-1)+edges) + 48*cells;
434   order= sort(pattern);
435   xyz= xyz(,order);
436   edges= edges(order);
437   pattern= pattern(order);
438   /* cells(order) is same as cells by construction */
439 
440   /* There remains only the question of splitting the points in
441    * a single cell into multiple disjoint polygons.
442    * To do this, we need one more precomputed array: poly_splits
443    * should be another 12-by-254 array with values between 0 and 3
444    * 0 for each edge on the first part, 1 for each edge on the
445    * second part, and so on up to 3 for each edge on the fourth
446    * part.  The value on unsliced edges can be anything, say 0.
447    * With a little cleverness poly_splits can be combined with
448    * poly_permutations, by putting poly_permutations =
449    * poly_permutations(as described above) + 12*poly_splits
450    * (this doesn't change the ordering of poly_permutations).
451    * I assume this has been done here: */
452   pattern/= 12;
453   /* now pattern jumps by 4 between cells, smaller jumps within cells
454    * get the list of places where a new value begins, and form a
455    * new pattern with values that increment by 1 between each plateau */
456   pattern= pattern(dif);
457   list= grow([1], where(pattern)+1);
458   pattern= (pattern!=0)(cum) + 1;
459 
460   nverts= histogram(pattern);
461   xyzverts= xyz;
462 
463   /* finally, deal with any fcolor function */
464   if (is_void(fcolor)) return [];
465 
466   /* if some polys have been split, need to split clist as well */
467   if (numberof(list)>numberof(clist)) clist= clist(cells(list));
468   if (!nointerp) {
469     if (is_func(fcolor))
470       return fcolor(m3, clist, lower, upper, fsl(1,), fsu(1,), cells);
471     else
472       return getc3(fcolor, m3, clist, lower, upper, fsl(1,), fsu(1,), cells);
473   } else {
474     if (is_func(fcolor)) return fcolor(m3, clist);
475     else return getc3(fcolor, m3, clist);
476   }
477 }
478 
_isosurface_slicer(m3,chunk)479 func _isosurface_slicer(m3, chunk)
480 {
481   return getv3(iso_index, m3, chunk)-value;
482 }
483 
_plane_slicer(m3,chunk)484 func _plane_slicer(m3, chunk)
485 {
486   return xyz3(m3, chunk)(+,..)*normal(+) - projection;
487 }
488 
to_corners3(list,ni,nj)489 func to_corners3(list, ni, nj)
490 /* DOCUMENT to_corners(list, ni, nj)
491      convert a LIST of cell indices in an (NI-1)-by-(NJ-1)-by-(nk-1)
492      logically rectangular grid of cells into the list of
493      2-by-2-by-2-by-numberof(LIST) cell corner indices in the
494      corresponding NI-by-NJ-by-nk list of vertices.
495  */
496 {
497   ninj= ni*nj;
498   list-= 1;
499   ii= list/(ni-1);
500   list+= ii + ni*(ii/(nj-1));
501   return ([1,2]+[0,ni](-,)+[0,ninj](-,-,)) + list(-,-,-,);
502 }
503 
504 /* The iterator3 function combines three distinct operations:
505  * (1) If only the M3 argument is given, return the initial
506  *     chunk of the mesh.  The chunk will be no more than
507  *     chunk3_limit cells of the mesh.
508  * (2) If only M3 and CHUNK are given, return the next CHUNK,
509  *     or [] if there are no more chunks.
510  * (3) If M3, CHUNK, and CLIST are all specified, return the
511  *     absolute cell index list corresponding to the index list
512  *     CLIST of the cells in the CHUNK.
513  *     Do not increment the chunk in this case.
514  *
515  * The form of the CHUNK argument and return value for cases (1)
516  * and (2) is not specified, but it must be recognized by the
517  * xyz3 and getv3 functions which go along with this iterator3.
518  * (For case (3), CLIST and the return value are both ordinary
519  *  index lists.)
520  */
iterator3(m3,chunk,clist)521 func iterator3(m3, chunk, clist)
522 {
523   return _car(_car(m3),4)(m3, chunk, clist);
524 }
525 
526 /* biggest temporary is 3 doubles times this,
527  * perhaps 4 or 5 doubles times this is most at one time */
528 chunk3_limit= 10000;
529 
iterator3_rect(m3,chunk,clist)530 func iterator3_rect(m3, chunk, clist)
531 {
532   if (is_void(chunk)) {
533     dims= _car(_car(m3,2));  /* [ni,nj,nk] cell dimensions */
534     ni= dims(1);
535     nj= dims(2);
536     nk= dims(3);
537     ninj= ni*nj;
538     if (chunk3_limit <= ni) {
539       /* stuck with 1D chunks */
540       ci= (ni-1)/chunk3_limit + 1;
541       cj= ck= 0;
542     } else if (chunk3_limit <= ninj) {
543       /* 2D chunks */
544       ci= ck= 0;
545       cj= (ninj-1)/chunk3_limit + 1;
546     } else {
547       /* 3D chunks */
548       ci= cj= 0;
549       ck= (ninj*nk-1)/chunk3_limit + 1;
550     }
551     chunk= [[ci==0,cj==0,ck==0],
552             [ni*((cj+ck)!=0),nj*(ck!=0)+(ci!=0),!ck],
553             [ci,cj,ck],[ni,nj,nk]];
554 
555   } else {
556     chunk= *chunk;
557     ni= chunk(1,4);  nj= chunk(2,4);  nk= chunk(3,4);
558     ninj= ni*nj;
559 
560     if (!is_void(clist)) {
561       /* add offset for this chunk to clist and return */
562       return [1,ni,ninj](+)*(chunk(,1)-1)(+) + clist;
563     }
564   }
565 
566   /* increment to next chunk */
567   xi= chunk(1,2);  xj= chunk(2,2);  xk= chunk(3,2);
568 
569   if ((np= chunk(1,3))) {
570     /* 1D chunks */
571     if (xi==ni) {
572       if (xj==nj) {
573         if (xk==nk) return [];
574         xk+= 1;
575         xj= 1;
576       } else {
577         xj+= 1;
578       }
579       xi= 0;
580     }
581     ci= xi+1;
582     step= ni/np;
583     frst= ni%np;   /* first frst steps are step+1 */
584     if (xi < (step+1)*frst) step+= 1;
585     xi+= step;
586     chunk(,1)= [ci,xj,xk];
587     chunk(,2)= [xi,xj,xk];
588 
589   } else if ((np= chunk(2,3))) {
590     if (xj==nj) {
591       if (xk==nk) return [];
592       xk+= 1;
593       xj= 0;
594     }
595     cj= xj+1;
596     step= nj/np;
597     frst= nj%np;   /* first frst steps are step+1 */
598     if (xj < (step+1)*frst) step+= 1;
599     xj+= step;
600     chunk(2:3,1)= [cj,xk];
601     chunk(2:3,2)= [xj,xk];
602 
603   } else {
604     if (xk==nk) return [];
605     ck= xk+1;
606     np= chunk(3,3);
607     step= nk/np;
608     frst= nk%np;   /* first frst steps are step+1 */
609     if (xk < (step+1)*frst) step+= 1;
610     xk+= step;
611     chunk(3,1)= ck;
612     chunk(3,2)= xk;
613   }
614 
615   /* return pointer to make chunk easy to recognize for xyz3, getv3 */
616   return &chunk;
617 }
618 
xyz3(m3,chunk)619 func xyz3(m3, chunk)
620 /* DOCUMENT xyz3(m3, chunk)
621 
622      return vertex coordinates for CHUNK of 3D mesh M3.  The CHUNK
623      may be a list of cell indices, in which case xyz3 returns a
624      3x2x2x2x(dimsof(CHUNK)) list of vertex coordinates.  CHUNK may
625      also be a mesh-specific data structure used in the slice3
626      routine, in which case xyz3 may return a 3x(ni)x(nj)x(nk)
627      array of vertex coordinates.  For meshes which are logically
628      rectangular or consist of several rectangular patches, this
629      is up to 8 times less data, with a concomitant performance
630      advantage.  Use xyz3 when writing slicing functions or coloring
631      functions for slice3.
632 
633    SEE ALSO: slice3, mesh3, getv3, getc3
634  */
635 {
636   xyz= _car(_car(m3),1)(m3, chunk);
637   extern _xyz3;
638   /* the cost of the following copy operation could be saved
639    * by making _xyz3 a pointer, but then we would be relying on
640    * the fslice caller of xyz3 not using the returned array for
641    * scratch space - leave it as is for now */
642   if (_xyz3_save) _xyz3= xyz;
643   return xyz;
644 }
645 
xyz3_rect(m3,chunk)646 func xyz3_rect(m3, chunk)
647 {
648   m3= _car(m3,2);
649   if (structof(chunk)==pointer) {
650     c= *chunk;
651     return _car(m3,2)(,c(1,1):1+c(1,2),c(2,1):1+c(2,2),c(3,1):1+c(3,2));
652   } else {
653     dims= _car(m3);
654     return _car(m3,2)(,to_corners3(chunk,dims(1)+1,dims(2)+1));
655   }
656 }
657 
xyz3_unif(m3,chunk)658 func xyz3_unif(m3, chunk)
659 {
660   m3= _car(m3,2);
661   n= _car(m3,2);
662   dxdydz= n(,1);
663   x0y0z0= n(,2);
664   if (structof(chunk)==pointer) {
665     c= *chunk;
666     i= c(,1)-1;
667     n= c(,2)+1-i;
668     xyz= array(x0y0z0+i*dxdydz, n(1), n(2), n(3));
669     xyz(1,..)+= span(0.,dxdydz(1),n(1));
670     xyz(2,..)+= span(0.,dxdydz(2),n(2))(-,);
671     xyz(3,..)+= span(0.,dxdydz(3),n(3))(-,-,);
672     return xyz;
673   } else {
674     dims= _car(m3);
675     ni= dims(1);
676     nj= dims(2);
677     ninj= ni*nj;
678     ijk= (chunk-1)(-:1:3,-,-,-,..);
679     ijk(3,..)/= ninj;
680     ijk(2,..)%= ninj;
681     ijk(2,..)/= ni;
682     ijk(1,..)%= ni;
683     ijk+= [[[[0,0,0],[1,0,0]],[[0,1,0],[1,1,0]]],
684            [[[0,0,1],[1,0,1]],[[0,1,1],[1,1,1]]]];
685     return x0y0z0 + ijk*dxdydz;
686   }
687 }
688 
getv3(i,m3,chunk)689 func getv3(i, m3, chunk)
690 /* DOCUMENT getv3(i, m3, chunk)
691 
692      return vertex values of the Ith function attached to 3D mesh M3
693      for cells in the specified CHUNK.  The CHUNK may be a list of
694      cell indices, in which case getv3 returns a 2x2x2x(dimsof(CHUNK))
695      list of vertex coordinates.  CHUNK may also be a mesh-specific data
696      structure used in the slice3 routine, in which case getv3 may
697      return a (ni)x(nj)x(nk) array of vertex values.  For meshes which
698      are logically rectangular or consist of several rectangular
699      patches, this is up to 8 times less data, with a concomitant
700      performance advantage.  Use getv3 when writing slicing functions
701      for slice3.
702 
703    SEE ALSO: slice3, mesh3, getc3, xyz3
704  */
705 {
706   return _car(_car(m3),2)(i, m3, chunk);
707 }
708 
getv3_rect(i,m3,chunk)709 func getv3_rect(i, m3, chunk)
710 {
711   fi= _cdr(m3,2);
712   m3= _car(m3,2);
713   if (i<1 || i>_len(fi)) error, "no such mesh function as F"+pr1(i);
714   dims= _car(m3);
715   if (allof(dimsof(_car(fi,i))(2:4)==dims)) {
716     error, "mesh function F"+pr1(i)+" is not vertex-centered";
717   }
718   if (structof(chunk)==pointer) {
719     c= *chunk;
720     return _car(fi,i)(c(1,1):1+c(1,2),c(2,1):1+c(2,2),c(3,1):1+c(3,2));
721   } else {
722     return _car(fi,i)(to_corners3(chunk,dims(1)+1,dims(2)+1));
723   }
724 }
725 
getc3(i,m3,chunk,l,u,fsl,fsu,cells)726 func getc3(i, m3, chunk, l, u, fsl, fsu, cells)
727 /* DOCUMENT getc3(i, m3, chunk)
728          or getc3(i, m3, clist, l, u, fsl, fsu, cells)
729 
730      return cell values of the Ith function attached to 3D mesh M3
731      for cells in the specified CHUNK.  The CHUNK may be a list of
732      cell indices, in which case getc3 returns a (dimsof(CHUNK))
733      list of vertex coordinates.  CHUNK may also be a mesh-specific data
734      structure used in the slice3 routine, in which case getc3 may
735      return a (ni)x(nj)x(nk) array of vertex values.  There is no
736      savings in the amount of data for such a CHUNK, but the gather
737      operation is cheaper than a general list of cell indices.
738      Use getc3 when writing colorng functions for slice3.
739 
740      If CHUNK is a CLIST, the additional arguments L, U, FSL, and FSU
741      are vertex index lists which override the CLIST if the Ith attached
742      function is defined on mesh vertices.  L and U are index lists into
743      the 2x2x2x(dimsof(CLIST)) vertex value array, say vva, and FSL
744      and FSU are corresponding interpolation coefficients; the zone
745      centered value is computed as a weighted average of involving these
746      coefficients.  The CELLS argument is required by histogram to do
747      the averaging.  See the source code for details.
748      By default, this conversion (if necessary) is done by averaging
749      the eight vertex-centered values.
750 
751    SEE ALSO: slice3, mesh3, getv3, xyz3
752  */
753 {
754   return _car(_car(m3),3)(i, m3, chunk, l, u, fsl, fsu, cells);
755 }
756 
getc3_rect(i,m3,chunk,l,u,fsl,fsu,cells)757 func getc3_rect(i, m3, chunk, l, u, fsl, fsu, cells)
758 {
759   fi= _cdr(m3,2);
760   m3= _car(m3,2);
761   if (i<1 || i>_len(fi)) error, "no such mesh function as F"+pr1(i);
762   dims= _car(m3);
763   if (allof(dimsof(_car(fi,i))(2:4)==dims)) {
764     if (structof(chunk)==pointer) {
765       c= *chunk;
766       return _car(fi,i)(c(1,1):c(1,2),c(2,1):c(2,2),c(3,1):c(3,2));
767     } else {
768       return _car(fi,i)(chunk);
769     }
770   } else {
771     if (structof(chunk)==pointer) {
772       c= *chunk;
773       return _car(fi,i)(zcen:c(1,1):1+c(1,2),zcen:c(2,1):1+c(2,2),
774                         zcen:c(3,1):1+c(3,2));
775     } else {
776       corners= _car(fi,i)(to_corners3(chunk,dims(1)+1,dims(2)+1));
777       if (is_void(l)) {
778         return 0.125*corners(sum:1:8,1,1,..);
779       } else {
780         /* interpolate corner values to get edge values */
781         corners= (corners(l)*fsu-corners(u)*fsl)/(fsu-fsl);
782         /* average edge values (vertex values of polys) on each poly */
783         return histogram(cells,corners)/histogram(cells);
784       }
785     }
786   }
787 }
788 
789 /* ------------------------------------------------------------------------ */
790 
791 /* There are 254 possible combinations of 8 vertices above and below
792  * the slicing plane (not counting all above or all below).  */
_construct3(void)793 func _construct3(void)
794 {
795   /* construct the edge list for each of the 254 possibilities */
796   i= indgen(254);
797   below= transpose([[[i&1,i&2],[i&4,i&8]],[[i&16,i&32],[i&64,i&128]]],0);
798   below= (below!=0);
799   mask= array(0n, 2,2,3,254);
800   mask(-,,,1,)= abs(below(dif,,,));
801   mask(,-,,2,)= abs(below(,dif,,));
802   mask(,,-,3,)= abs(below(,,dif,));
803 
804   /* walking the edges requires some connectivity information */
805   start_face= [3,4,3,4, 1,2,1,2, 1,2,1,2];
806   face_edges= [[5,11,7,9], [6,10,8,12],
807                [1,9,3,10], [2,12,4,11],
808                [1,6,2,5],  [3,7,4,8]];
809   edge_faces= [[3,5],[4,5],[3,6],[4,6],[1,5],[2,5],
810                [1,6],[2,6],[1,3],[2,3],[1,4],[2,4]];
811 
812   permute= array('\0', 12, 254);
813   for (i=1 ; i<=254 ; ++i) permute(,i)= _walk3(mask(*,i));
814   return permute;
815 }
816 
_walk3(mask)817 func _walk3(mask)
818 {
819   permute= splits= array(0, 12);
820   split= 0;
821   list= where(mask);
822   edge= min(list);
823   face= start_face(edge);
824 
825   for (i=0 ; i<numberof(list)-1 ; ++i) {
826     /* record this edge */
827     permute(edge)= i;
828     splits(edge)= split;
829     mask(edge)= 0;
830 
831     /* advance to next edge */
832     try= face_edges(,face);
833     now= abs(try-edge)(mnx);
834     /* test opposite edge first (make X, never // or \\) */
835     edge= try((now+1)%4+1);
836     if (!mask(edge)) {
837       /* otherwise one or the other opposite edges must be set */
838       edge= try(now%4+1);
839       if (!mask(edge)) {
840         edge= try((now+2)%4+1);
841         if (!mask(edge)) {
842           /* we must have closed a loop */
843           split+= 1;
844           edge= min(where(mask));
845         }
846       }
847     }
848     try= edge_faces(,edge);
849     now= try(1);
850     face= face==now? try(2) : now;
851   }
852   /* last pass through loop */
853   permute(edge)= i;
854   splits(edge)= split;
855   mask(edge)= 0;
856 
857   return permute+12*splits;
858 }
859 
860 poly_permutations= _construct3();
861 
862 /* ------------------------------------------------------------------------ */
863 
864 func slice2x(plane, &nverts, &xyzverts, &values, &nvertb, &xyzvertb, &valueb)
865 /* DOCUMENT slice2, plane, nverts, values, xyzverts
866 
867      Slice a polygon list, retaining only those polygons or
868      parts of polygons on the positive side of PLANE, that is,
869      the side where xyz(+)*PLANE(+:1:3)-PLANE(4) > 0.0.
870      The NVERTS, VALUES, and XYZVERTS arrays serve as both
871      input and output, and have the meanings of the return
872      values from the slice3 function.
873 
874    SEE ALSO: slice2, slice2_precision
875  */
876 {
877   _slice2x= 1;
878   return slice2(plane, nverts, xyzverts, values, nvertb, xyzvertb, valueb);
879 }
880 
881 func slice2(plane, &nverts, &xyzverts, &values, &nvertb, &xyzvertb, &valueb)
882 /* DOCUMENT slice2, plane, nverts, xyzverts
883          or slice2, plane, nverts, xyzverts, values
884 
885      Slice a polygon list, retaining only those polygons or
886      parts of polygons on the positive side of PLANE, that is,
887      the side where xyz(+)*PLANE(+:1:3)-PLANE(4) > 0.0.
888      The NVERTS, XYZVERTS, and VALUES arrays serve as both
889      input and output, and have the meanings of the return
890      values from the slice3 function.  It is legal to omit the
891      VALUES argument (e.g.- if there is no fcolor function).
892 
893      In order to plot two intersecting slices, one could
894      slice (for example) the horizontal plane twice (slice2x) -
895      first with the plane of the vertical slice, then with minus
896      that same plane.  Then, plot first the back part of the
897      slice, then the vertical slice, then the front part of the
898      horizontal slice.  Of course, the vertical plane could
899      be the one to be sliced, and "back" and "front" vary
900      depending on the view point, but the general idea always
901      works.
902 
903    SEE ALSO: slice3, plane3, slice2x, slice2_precision
904  */
905 {
906   have_values= !is_void(values);
907 
908   /* get the list of indices into nverts (or values) for each of
909    * the points in xyzverts */
910   ndxs= histogram(1+nverts(psum))(1:-1);
911   ndxs(1)+= 1;
912   ndxs= ndxs(psum);
913 
914   /* form dot products of all the points with the given plane */
915   dp= xyzverts(+,)*plane(+:1:3) - plane(4);
916 
917   /* separate into lists of unclipped and partially clipped polys */
918   if (!slice2_precision) {
919     /* if precision is not set, slice exactly at dp==0.0, with
920      * any points exactly at dp==0.0 treated as if they had dp>0.0 */
921     keep= (dp>=0.0);
922   } else {
923     /* if precision is set, polygons are clipped to +-precision,
924      * so that any poly crossing +precision is clipped to dp>=+precision,
925      * any poly crossing -precision is clipped to dp<=-precision, and
926      * any poly lying entirely between +-precision is discarded entirely */
927     keep= (dp>=slice2_precision);
928   }
929   nkeep= long(histogram(ndxs, keep));
930   mask0= (nkeep==nverts);
931   mask1= (nkeep!=0 & !mask0);
932   list1= where(mask1);
933   if (numberof(list1)) {
934     nvertc= nverts(list1);
935     if (have_values) valuec= values(list1);
936     list= where(mask1(ndxs));
937     xyzc= xyzverts(, list);
938   }
939   if (_slice2x) {
940     if (!slice2_precision) {
941       mask2= !nkeep;
942       nvertc0= nvertc;
943       valuec0= valuec;
944       xyzc0= xyzc;
945     } else {
946       keep2= (dp>-slice2_precision);
947       nkeep2= long(histogram(ndxs, keep2));
948       mask2= (nkeep!=0 & nkeep<nverts);
949       list2= where(mask2);
950       if (numberof(list2)) {
951         nvertc0= nverts(list2);
952         if (have_values) valuec0= values(list2);
953         listc= where(mask2(ndxs));
954         xyzc0= xyzverts(, listc);
955       }
956       mask2= !nkeep2;
957     }
958     list2= where(mask2);
959     if (numberof(list2)) {
960       nvertb= nverts(list2);
961       if (have_values) valueb= values(list2);
962       xyzvertb= xyzverts(, where(mask2(ndxs)));
963     } else {
964       nvertb= valueb= xyzvertb= [];
965     }
966   }
967   list0= where(mask0);
968   if (numberof(list0)<numberof(nverts)) {
969     nverts= nverts(list0);
970     if (have_values) values= values(list0);
971     xyzverts= xyzverts(, where(mask0(ndxs)));
972   }
973 
974   /* done if no partially clipped polys */
975   if (!numberof(list1) && !numberof(listc)) return;
976   if (!numberof(list1)) goto skip;
977 
978   /* get dot products and keep list for the clipped polys */
979   dp= dp(list);
980   if (slice2_precision) dp-= slice2_precision;
981   keep= (dp>=0.0);
982 
983   /* get the indices of the first and last points in each clipped poly */
984   last= nvertc(psum);
985   frst= last - nvertc + 1;
986 
987   /* get indices of previous and next points in cyclic order */
988   prev= next= indgen(0:numberof(keep)-1);
989   prev(frst)= last;
990   next(prev)= indgen(numberof(keep));
991 
992   _slice2_part;
993 
994   grow, nverts, nvertc;
995   if (have_values) grow, values, valuec;
996   grow, xyzverts, xyzc;
997 
998   if (_slice2x) {
999   skip:
1000     nvertc= nvertc0;
1001     valuec= valuec0;
1002     xyzc= xyzc0;
1003     if (!slice2_precision) {
1004       keep= !keep;
1005     } else {
1006       dp= dp(listc)+slice2_precision;
1007       keep= (dp>=0.0);
1008     }
1009 
1010     _slice2_part;
1011 
1012     grow, nvertb, nvertc;
1013     if (have_values) grow, valueb, valuec;
1014     grow, xyzvertb, xyzc;
1015   }
1016 }
1017 
1018 local slice2_precision;
1019 /* DOCUMENT slice2_precision= precision
1020      Controls how slice2 (or slice2x) handles points very close to
1021      the slicing plane.  PRECISION should be a positive number or zero.
1022      Zero PRECISION means to clip exactly to the plane, with points
1023      exactly on the plane acting as if they were slightly on the side
1024      the normal points toward.  Positive PRECISION means that edges
1025      are clipped to parallel planes a distance PRECISION on either
1026      side of the given plane.  (Polygons lying entirely between these
1027      planes are completely discarded.)
1028 
1029      Default value is 0.0.
1030 
1031    SEE ALSO: slice2, slice2x
1032  */
1033 slice2_precision= 0.0;
1034 
1035 func _slice2_part
1036 {
1037   extern nvertc, xyzc;
1038 
1039   /* find the points where any edges of the polys cut the plane */
1040   mask0= (!keep) & keep(next);   /* off-->on */
1041   list0= where(mask0);
1042   if (numberof(list0)) {
1043     list= next(list0);
1044     dpl= dp(list0)(-,);
1045     dpu= dp(list)(-,);
1046     xyz0= (xyzc(,list0)*dpu-xyzc(,list)*dpl)/(dpu-dpl);
1047   }
1048   mask1= (!keep) & keep(prev);   /* on-->off */
1049   list1= where(mask1);
1050   if (numberof(list1)) {
1051     list= prev(list1);
1052     dpl= dp(list1)(-,);
1053     dpu= dp(list)(-,);
1054     xyz1= (xyzc(,list1)*dpu-xyzc(,list)*dpl)/(dpu-dpl);
1055   }
1056 
1057   /* form an index list xold which gives the indices in the original
1058    * xyzc list at the places in the new, sliced xyzc list */
1059   mask= keep+mask0+mask1;  /* 0, 1, or 2 */
1060   list= mask(psum);  /* index values in new list */
1061   xold= array(0, list(0));
1062   mlist= where(mask);
1063   xold(list(mlist))= mlist;
1064   dups= where(mask==2);
1065   if (numberof(dups)) xold(list(dups)-1)= dups;
1066 
1067   /* form the new, sliced xyzc vertex list */
1068   xyzc= xyzc(,xold);
1069   if (numberof(list0)) xyzc(,list(list0))= xyz0;
1070   if (numberof(list1)) xyzc(,list(list1)-mask(list1)+1)= xyz1;
1071 
1072   /* get the list of indices into nvertc (or valuec) for each of
1073    * the points in xyzc */
1074   ndxs= histogram(1+last)(1:-1);
1075   ndxs(1)+= 1;
1076   ndxs= ndxs(psum);
1077   /* compute the new number of vertices */
1078   nvertc= histogram(ndxs(xold));
1079 }
1080 
1081 /* ------------------------------------------------------------------------ */
1082 
1083 func pl3surf(nverts, xyzverts, values, cmin=, cmax=)
1084 /* DOCUMENT pl3surf, nverts, xyzverts
1085          or pl3surf, nverts, xyzverts, values
1086 
1087      Perform simple 3D rendering of an object created by slice3
1088      (possibly followed by slice2).  NVERTS and XYZVERTS are polygon
1089      lists as returned by slice3, so XYZVERTS is 3-by-sum(NVERTS),
1090      where NVERTS is a list of the number of vertices in each polygon.
1091      If present, the VALUES should have the same length as NVERTS;
1092      they are used to color the polygon.  If VALUES is not specified,
1093      the 3D lighting calculation set up using the light3 function
1094      will be carried out.  Keywords cmin= and cmax= as for plf, pli,
1095      or plfp are also accepted.  (If you do not supply VALUES, you
1096      probably want to use the ambient= keyword to light3 instead of
1097      cmin= here, but cmax= may still be useful.)
1098 
1099    SEE ALSO: pl3tree, slice3, slice2, rot3, light3
1100  */
1101 {
1102   require, "pl3d.i";
1103   if (_draw3) {
1104     list= nverts;
1105     nverts= _nxt(list);
1106     xyzverts= _nxt(list);
1107     values= _nxt(list);
1108     cmin= _nxt(list);
1109     cmax= _nxt(list);
1110 
1111     local x, y, z, list, vlist;
1112     get3_xy, xyzverts, x, y, z, 1;
1113     if (is_void(values)) {
1114       xyzverts(1,..)= x;
1115       xyzverts(2,..)= y;
1116       xyzverts(3,..)= z;
1117       values= get3_light(xyzverts, nverts);
1118     }
1119     sort3d, z, nverts, list, vlist;
1120     nverts= nverts(list);
1121     values= values(list);
1122     x= x(vlist);
1123     y= y(vlist);
1124 
1125     plfp, values,y,x,nverts, cmin=cmin,cmax=cmax, legend=string(0);
1126     return;
1127   }
1128 
1129   nverts+= 0;
1130   xyzverts+= 0.0;
1131 
1132   if (numberof(xyzverts)!=3*sum(nverts) || anyof(nverts<3) ||
1133       structof(nverts)!=long)
1134     error, "illegal or inconsistent polygon list";
1135   if (!is_void(values) && numberof(values)!=numberof(nverts))
1136     error, "illegal or inconsistent polygon color values";
1137 
1138   if (!is_void(values)) values+= 0.0;
1139 
1140   clear3;
1141   set3_object, pl3surf, _lst(nverts,xyzverts,values,cmin,cmax);
1142 }
1143 
1144 /* ------------------------------------------------------------------------ */
1145 
1146 func pl3tree(nverts, xyzverts, values, plane, cmin=, cmax=)
1147 /* DOCUMENT pl3tree, nverts, xyzverts
1148          or pl3tree, nverts, xyzverts, values, plane
1149 
1150      Add the polygon list specified by NVERTS (number of vertices in
1151      each polygon) and XYZVERTS (3-by-sum(NVERTS) vertex coordinates)
1152      to the currently displayed b-tree.  If VALUES is specified, it
1153      must have the same dimension as NVERTS, and represents the color
1154      of each polygon.  If VALUES is not specified, the polygons
1155      are assumed to form an isosurface which will be shaded by the
1156      current 3D lighting model; the isosurfaces are at the leaves of
1157      the b-tree, sliced by all of the planes.  If PLANE is specified,
1158      the XYZVERTS must all lie in that plane, and that plane becomes
1159      a new slicing plane in the b-tree.
1160 
1161      Each leaf of the b-tree consists of a set of sliced isosurfaces.
1162      A node of the b-tree consists of some polygons in one of the
1163      planes, a b-tree or leaf entirely on one side of that plane, and
1164      a b-tree or leaf on the other side.  The first plane you add
1165      becomes the root node, slicing any existing leaf in half.  When
1166      you add an isosurface, it propagates down the tree, getting
1167      sliced at each node, until its pieces reach the existing leaves,
1168      to which they are added.  When you add a plane, it also propagates
1169      down the tree, getting sliced at each node, until its pieces
1170      reach the leaves, which it slices, becoming the nodes closest to
1171      the leaves.
1172 
1173      This structure is relatively easy to plot, since from any
1174      viewpoint, a node can always be plotted in the order from one
1175      side, then the plane, then the other side.
1176 
1177      This routine assumes a "split palette"; the colors for the
1178      VALUES will be scaled to fit from color 0 to color 99, while
1179      the colors from the shading calculation will be scaled to fit
1180      from color 100 to color 199.  (If VALUES is specified as a char
1181      array, however, it will be used without scaling.)
1182      You may specifiy a cmin= or cmax= keyword to affect the
1183      scaling; cmin is ignored if VALUES is not specified (use the
1184      ambient= keyword from light3 for that case).
1185 
1186    SEE ALSO: pl3surf, slice3, slice2, rot3, light3, split_palette
1187  */
1188 {
1189   require, "pl3d.i";
1190   if (_draw3) {
1191     /* avoid overhead of local variables for _pl3tree and _pl3leaf */
1192     local x,y,z;
1193     local nverts, xyzverts, values, list, vlist, cmin, cmax;
1194     local nv, vv, xx, yy, zz, cmin, cmax;
1195     _pl3tree, nverts;
1196     return;
1197   }
1198 
1199   nverts= 0+nverts(*);
1200   xyzverts= double(xyzverts(,*));
1201   if (!is_void(values)) values= double(values(*));
1202   if (!is_void(plane)) plane= double(plane);
1203 
1204   if (numberof(xyzverts)!=3*sum(nverts) || anyof(nverts<3) ||
1205       structof(nverts)!=long)
1206     error, "illegal or inconsistent polygon list";
1207   if (!is_void(values) && numberof(values)!=numberof(nverts))
1208     error, "illegal or inconsistent polygon color values";
1209   if (!is_void(plane) && (dimsof(plane)(1)!=1 || numberof(plane)!=4))
1210     error, "illegal plane format, try plane3 function";
1211 
1212   leaf= _lst(_lst(nverts, xyzverts, values, cmin, cmax));
1213 
1214   /* retrieve current b-tree (if any) from 3D display list */
1215   tree= _cdr(_draw3_list, _draw3_n);
1216   if (is_void(tree) || _car(tree)!=pl3tree) {
1217     tree= _lst(plane, [], leaf, []);
1218   } else {
1219     tree= _car(tree,2);
1220     _pl3tree_add, leaf, plane, tree;
1221   }
1222 
1223   clear3;
1224   set3_object, pl3tree, tree;
1225 }
1226 
split_palette(name)1227 func split_palette(name)
1228 /* DOCUMENT split_palette
1229          or split_palette, "palette_name.gp"
1230      split the current palette or the specified palette into two
1231      parts; colors 0 to 99 will be a compressed version of the
1232      original, while colors 100 to 199 will be a gray scale.
1233    SEE ALSO: pl3tree, split_bytscl
1234  */
1235 {
1236   if (!is_void(name)) palette, name;
1237   local r,g,b;
1238   palette,query=1, r,g,b;
1239   n= numberof(r);
1240   if (n<100) {
1241     palette, "earth.gp";
1242     palette,query=1, r,g,b;
1243     n= numberof(r);
1244   }
1245   r= char(interp(r,indgen(n),span(1,n,100)));
1246   g= char(interp(g,indgen(n),span(1,n,100)));
1247   b= char(interp(b,indgen(n),span(1,n,100)));
1248   grow, r, char(span(0,255,100));
1249   grow, g, char(span(0,255,100));
1250   grow, b, char(span(0,255,100));
1251   palette, r,g,b;
1252 }
1253 
1254 func split_bytscl(x, upper, cmin=,cmax=)
1255 /* DOCUMENT split_bytscl(x, 0)
1256          or split_bytscl(x, 1)
1257      as bytscl function, but scale to the lower half of a split
1258      palette (0-99, normally the color scale) if the second parameter
1259      is zero or nil, or the upper half (100-199, normally the gray
1260      scale) if the second parameter is non-zero.
1261    SEE ALSO: split_palette
1262  */
1263 {
1264   x= bytscl(x,cmin=cmin,cmax=cmax,top=99);
1265   if (upper) x+= char(100);
1266   return x;
1267 }
1268 
_pl3tree(tree)1269 func _pl3tree(tree)
1270 {
1271   /* avoid overhead of local variables for _pl3tree and _pl3leaf */
1272   extern x,y,z;
1273 
1274   /* tree is a 4-element list like this:
1275    * _lst(plane, back_tree, inplane_leaf, front_tree)
1276    * plane= _car(tree)  is void if this is just a leaf
1277    *                    in which case, only inplane_leaf is not void
1278    * back_tree= _car(tree,2)    is the part behind plane
1279    * inplane_leaf= _car(tree,3) is the part in the plane itself
1280    * front_tree= _car(tree,4)   is the part in front of plane
1281    */
1282   if (is_void(tree)) return;
1283   if (is_void(_car(tree))) {
1284     /* only the leaf is non-nil (but not a plane) */
1285     _pl3leaf, _car(tree,3), 1;
1286     return;
1287   }
1288 
1289   /* apply the 3D coordinate transform to two points along the
1290    * normal of the splitting plane to judge which is in front */
1291   get3_xy, [[0.,0.,0.],_car(tree)(1:3)], x,y,z, 1;
1292 
1293   /* plot the parts in order toward the current viewpoint */
1294   if (z(2) >= z(1)) {
1295     _pl3tree, _car(tree,2);
1296     _pl3leaf, _car(tree,3), 0;
1297     _pl3tree, _car(tree,4);
1298   } else {
1299     _pl3tree, _car(tree,4);
1300     _pl3leaf, _car(tree,3), 0;
1301     _pl3tree, _car(tree,2);
1302   }
1303 }
1304 
_pl3leaf(leaf,not_plane)1305 func _pl3leaf(leaf, not_plane)
1306 {
1307   /* avoid overhead of local variables for _pl3tree and _pl3leaf */
1308   extern x,y,z;
1309   extern nverts, xyzverts, values, list, vlist;
1310 
1311   /* count number of polys, number of vertices */
1312   nverts= xyzverts= 0;
1313   _map, _pl3tree_count, leaf;
1314 
1315   /* accumulate polys and vertices into a single polygon list */
1316   values= array(char, nverts);
1317   nverts= array(0, nverts);
1318   x= y= array(0.0, xyzverts);
1319   if (not_plane) z= x;
1320   else z= [];
1321   list= vlist= 1;
1322   _map, _pl3tree_accum, leaf;
1323 
1324   /* sort the single polygon list */
1325   if (not_plane) {
1326     sort3d, z, nverts, list, vlist;
1327     nverts= nverts(list);
1328     values= values(list);
1329     x= x(vlist);
1330     y= y(vlist);
1331   }
1332 
1333   plfp, values,y,x,nverts, legend=string(0);
1334 }
1335 
_pl3tree_count(item)1336 func _pl3tree_count(item)
1337 {
1338   nverts+= numberof(_nxt(item));
1339   xyzverts+= numberof(_nxt(item))/3;
1340 }
1341 
_pl3tree_accum(item)1342 func _pl3tree_accum(item)
1343 {
1344   /* avoid overhead of local variables for _pl3tree and _pl3leaf */
1345   extern x,y,z;
1346   extern nverts, xyzverts, values, list, vlist;
1347   extern nv, vv, xx, yy, zz, cmin, cmax;
1348 
1349   nv= _nxt(item);
1350   xyzverts= _nxt(item);
1351   vv= _nxt(item);
1352   cmin= _nxt(item);
1353   cmax= _nxt(item);
1354 
1355   if (is_void(vv)) {
1356     /* this is an isosurface to be shaded */
1357     get3_xy, xyzverts, xx, yy, zz, 1;
1358     xyzverts(1,..)= xx;
1359     xyzverts(2,..)= yy;
1360     xyzverts(3,..)= zz;
1361     vv= get3_light(xyzverts, nv);
1362     vv= split_bytscl(vv, 1, cmin=0.0, cmax=cmax);
1363   } else {
1364     /* this is to be pseudo-colored */
1365     if (not_plane) get3_xy, xyzverts, xx, yy, zz, 1;
1366     else get3_xy, xyzverts, xx, yy;
1367     if (structof(vv)!=char)
1368       vv= split_bytscl(vv, 0, cmin=cmin, cmax=cmax);
1369   }
1370 
1371   /* accumulate nverts and values */
1372   item= numberof(nv)-1;
1373   nverts(list:list+item)= nv;
1374   values(list:list+item)= vv;
1375   list+= item+1;
1376 
1377   /* accumulate x, y, and z */
1378   item= numberof(xx)-1;
1379   x(vlist:vlist+item)= xx;
1380   y(vlist:vlist+item)= yy;
1381   if (not_plane) z(vlist:vlist+item)= zz;
1382   vlist+= item+1;
1383 }
1384 
_pl3tree_add(leaf,plane,tree)1385 func _pl3tree_add(leaf, plane, tree)
1386 {
1387   if (!is_void(_car(tree))) {
1388     /* tree has slicing plane, slice new leaf or plane and descend */
1389     back= _pl3tree_slice(_car(tree), leaf);
1390     if (back) {
1391       if (_car(tree,2)) _pl3tree_add, back, plane, _car(tree,2);
1392       else _car, tree, 2, _lst([], [], back, []);
1393     }
1394     if (leaf) {
1395       if (_car(tree,4)) _pl3tree_add, leaf, plane, _car(tree,4);
1396       else _car, tree, 4, _lst([], [], leaf, []);
1397     }
1398 
1399   } else if (!is_void(plane)) {
1400     /* tree is just a leaf, but this leaf has slicing plane */
1401     _car, tree, 1, plane;
1402     leaf= _car(tree, 3, leaf);  /* swap new leaf with original leaf */
1403     back= _pl3tree_slice(plane, leaf);   /* slice old leaf by plane */
1404     if (back) _car, tree, 2, _lst([], [], back, []);
1405     if (leaf) _car, tree, 4, _lst([], [], leaf, []);
1406 
1407   } else {
1408     /* tree is just a leaf and this leaf has no slicing plane */
1409     _cdr, leaf, 1, _car(tree, 3, leaf);
1410   }
1411 }
1412 
1413 func _pl3tree_slice(plane, &leaf)
1414 {
1415   back= frnt= [];
1416   for ( ; leaf ; leaf=_cdr(leaf)) {
1417     ll= _car(leaf);
1418     /* each item in the leaf list is itself a list */
1419     nvf= nvb= _nxt(ll);
1420     xyzf= xyzb= _nxt(ll);
1421     valf= valb= _nxt(ll);
1422     slice2x, plane, nvf, xyzf, valf, nvb, xyzb, valb;
1423     if (numberof(nvf))
1424       frnt= _cat(_lst(_cat(nvf,xyzf,_lst(valf),ll)),frnt);
1425     if (numberof(nvb))
1426       back= _cat(_lst(_cat(nvb,xyzb,_lst(valb),ll)),back);
1427   }
1428   leaf= frnt;
1429   return back;
1430 }
1431 
1432 #if 0
1433 func pl3tree_prt(void)
1434 {
1435   tree= _cdr(_draw3_list, _draw3_n);
1436   if (is_void(tree) || _car(tree)!=pl3tree) {
1437     write, "<current 3D display not a pl3tree>";
1438   } else {
1439     tree= _car(tree,2);
1440     _pl3tree_prt,tree,0
1441   }
1442 }
1443 
1444 func _pl3tree_prt(tree,depth)
1445 {
1446   if (is_void(tree)) return;
1447   indent= strpart("                               ",1:1+2*depth);
1448   indent= strpart(indent, 1:-1);
1449   write, indent+"+DEPTH= "+pr1(depth);
1450   if (_len(tree)!=4) write, indent+"***error - not a tree";
1451   write, indent+"plane= "+pr1(_car(tree));
1452   back= _car(tree,2);
1453   list= _car(tree,3);
1454   frnt= _car(tree,4);
1455   if (is_void(back)) write, indent+"back= []";
1456   else _pl3tree_prt, back, depth+1;
1457 
1458   while (list) {
1459     leaf= _nxt(list);
1460     write, indent+"leaf length= "+pr1(_len(leaf));
1461     write, indent+"npolys= "+pr1(numberof(_car(leaf)))+
1462       ", nverts= "+pr1(sum(_nxt(leaf)));
1463     write, indent+"nverts= "+pr1(numberof(_nxt(leaf))/3)+
1464       ", nvals= "+pr1(numberof(_nxt(leaf)));
1465   }
1466 
1467   if (is_void(frnt)) write, indent+"frnt= []";
1468   else _pl3tree_prt, frnt, depth+1;
1469   write, indent+"-DEPTH= "+pr1(depth);
1470 }
1471 #endif
1472 
1473 /* ------------------------------------------------------------------------ */
1474