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