chiark / gitweb /
turn it the right way up
[moebius2.git] / output.c
1 /*
2  * Generates an STL file
3  *  usage: ./output+<size> <thickness> <unit-circle-diameter>
4  *    thickness is in original moebius unit circle units
5  *    unit circle diameter is in whatever dimensions the STL file uses
6  *
7  * We have to:
8  *   - add some thickness
9  *   - define a shape for the rim so that it is solid
10  *   - scale the coordinates
11  *   - translate the coordinates so they're all positive
12  */
13 /*
14  *  ./output-64 <dense-64.cfm 0.1 50 >t.stl
15  *  meshlab t.stl
16  */
17 /*
18  * Re STL, see:
19  *  http://www.ennex.com/~fabbers/StL.asp
20  *  http://en.wikipedia.org/wiki/STL_%28file_format%29
21  *    saved here as stl-format-wikipedia.txt
22  */
23
24 /*
25  * We add thickness by adding vertices as follows:
26  *
27  *                     :axis of symmetry
28  *             sides | :
29  *               swap| :
30  *                   | :
31  *       *F          | : *F              *F              *F              *F
32  *                   | :
33  *      *E      *G   | :*E      *G      *E      *G      *E      *G      *E
34  *                   | :
35  *    *D ____________|*D ____________ *D ____________ *D ____________ *D __
36  *    /\          3  |/\   0          /\              /\              /\
37  *   /  \            / :\            /  \            /  \            /  \
38  *       \    *A   4/ \: \5   *B    /    \    *B    /    \    *B    /    \
39  *        \        /   \  \    1   /      \        /      \        /
40  *         \      /    :\  \      /        \      /        \      /
41  *    *B    \    /    *B2|  \2  1/   0*A    \    /    *A    \    /    *A
42  *           \  /      : |   \  / . '        \  /            \  /
43  *   _______  *C _____ :_|___ *C' __________  *C ___________  *C _________
44  *            /\       : | 3  /\.  0          /\              /\
45  *           /  \      :3|   /  \ ` .        /  \            /  \
46  *    *A    /    \    *A |  /4  5\   5*B    /    \    *B    /    \    *B
47  *         /      \   1: / /      \        /      \        /      \
48  *        /        \   :/ /    4   \      /        \      /        \
49  *       /    *B2  2\  / /1   *A    \    /    *A    \    /    *A    \    /
50  *   \  /            \/:/     0      \  /            \  /            \  /
51  *    *C ____________|*C ____________ *C ___________  *C ___________  *C __
52  *    /\          3  |/\   0          /\              /\              /\
53  *   /  \            / :\            /  \            /  \            /  \
54  *       \    *A3  4/ \: \5  5*B    /    \    *B    /    \    *B    /    \
55  *        \        /   \  \        /      \        /      \        /
56  *         \      /   4:\  \      /        \      /        \      /
57  *    *B    \    /    *B |  \    /    *A    \    /    *A    \    /    *A
58  *           \  /      : |   \  / . '        \  /            \  /
59  *   _______  *C _____ :_|___ *C' __________  *C ___________  *C _________
60  *            /\       : | 3  /\   0          /\              /\
61  *           /  \      : |   /  \            /  \            /  \
62  *                     :
63  *         .      .    :   .      .        .      .        .      .
64  *                     :
65
66  *           \  /      : |   \  /            \  /            \  /
67  *   _______  *C _____ :_|___ *C ___________  *C ___________  *C _________
68  *            /\       : | 3  /\   0          /\              /\
69  *           /  \      : |   /  \            /  \            /  \
70  *    *A    /    \    *A |  /4  5\    *B    /    \    *B    /    \    *B
71  *         /      \   1: / /      \        /      \        /      \
72  *        /        \   :/ /        \      /        \      /        \
73  *       /    *B2  2\  / /1  0*A    \    /    *A    \    /    *A    \    /
74  *   \  /            \/:/            \  /            \  /            \  /
75  *    *D ____________|*D ____________ *D ___________  *D ___________  *D __
76  *                 3 | :   0
77  *      *E    *G     | :*E    *G        *E    *G        *E    *G        *E
78  *                   | :
79  *       *F          | : *F              *F              *F              *F
80  *                   | :
81  *
82  * Each A,B,C,D represents two vertices - one on each side of the
83  * surface.  Each E,F,G represents a several vertices in an arc around
84  * the rim.  Digits are `e' values for edges or relative AB
85  * outvertices.
86  *
87  * The constructions we use are:
88  *
89  *  A, B:  Extend the normal vector of the containing intriangle
90  *         from its centroid for thickness.
91  *
92  *  C:     Take mean position (centroid) of all surrounding
93  *         computed A and B, and extend from base point in
94  *         direction of that centroid for thickness.
95  *
96  *  D, E, F:
97  *         Compute notional rim vector R as mean of the two
98  *         adjoining rim edges, and consider plane P as
99  *         that passing through the base point normal to R.
100  *         Project the centroid of the two adjoining non-rim
101  *         vertices onto P.  Now D(EF)+ED is the semicircle
102  *         in P centred on the base point with radius thickness
103  *         and which is opposite that centroid.
104  *
105  *  G:     Each F is the mean of those two adjacent Es with the
106  *         same angle in their respective Ps.
107  *
108  * The outfacets are:
109  *
110  *  For each input vertex on each side, the six (or perhaps only three)
111  *  triangles formed by its C or D and the surrounding Cs and/or Ds.
112  *
113  *  For each G, the six triangles formed by that G and the adjacent
114  *  four Fs (or two Fs and two Ds) and two Es.
115  */
116
117 #include "mgraph.h"
118 #include "common.h"
119
120 /*---------- declarations and useful subroutines ----------*/
121
122 #define FOR_SIDE for (side=0; side<2; side++)
123
124 typedef struct {
125   double p[D3];
126 } OutVertex;
127
128 #define NG   10
129 #define NDEF (NG*2+1)
130
131 #define OUTPUT_ARRAY_LIST(DO_OUTPUT_ARRAY)      \
132   DO_OUTPUT_ARRAY(ovAB)                         \
133   DO_OUTPUT_ARRAY(ovC)                          \
134   DO_OUTPUT_ARRAY(ovDEF)                        \
135   DO_OUTPUT_ARRAY(ovG)
136
137 static OutVertex ovAB[N][2][2]; /* indices: vertex to W, A=0 B=1, side */
138 static OutVertex ovC[N][2];
139 static OutVertex ovDEF[X][2][NDEF]; /* x, !!y, angle */
140 static OutVertex ovG[X][2][NG]; /* x, !!y, angle; for G to the East of x */
141
142 static Vertices in;
143
144 static double thick; /* in input units */
145 static double scale;
146
147 static void outfacet(int rev, const OutVertex *a,
148                      const OutVertex *b, const OutVertex *c);
149
150 typedef int int_map(int);
151 static int defs_aroundmap_swap(int around) { return NDEF-1-around; }
152 static int int_identity_function(int i) { return i; }
153
154 static void normalise_thick(double a[D3]) {
155   /* multiplies a by a scalar so that its magnitude is thick */
156   int k;
157   double multby= thick / magnD(a);
158   K a[k] *= multby;
159 }
160
161 static void triangle_normal(double normal[D3], const double a[D3],
162                             const double b[D3], const double c[D3]) {
163   double ab[D3], ac[D3];
164   int k;
165   
166   K ab[k]= b[k] - a[k];
167   K ac[k]= c[k] - a[k];
168   xprod(normal,ab,ac);
169 }
170
171 static OutVertex *invertex2outvertexab(int v0, int e, int side) {
172   int vref, vchk, ab;
173   switch (e) {
174   case 0:  vref=v0;               vchk=EDGE_END2(v0,1);  ab=0;   break;
175   case 1:  vref=                  vchk=EDGE_END2(v0,2);  ab=1;   break;
176   case 2:  vref=EDGE_END2(v0,3);  vchk=EDGE_END2(v0,2);  ab=0;   break;
177   case 3:  vref=EDGE_END2(v0,3);  vchk=EDGE_END2(v0,4);  ab=1;   break;
178   case 4:  vref=                  vchk=EDGE_END2(v0,4);  ab=0;   break;
179   case 5:  vref=v0;               vchk=EDGE_END2(v0,5);  ab=1;   break;
180   default: abort();
181   }
182   if (vchk<0) return 0;
183   int sw= vertices_span_join_p(v0,vref);
184   return &ovAB[vref][ab^sw][side^sw];
185 }
186
187 /*---------- output vertices ----------*/
188
189 #define Ok(ov, value) ((ov).p[k]= outvertex_coord_check(value))
190
191 static double outvertex_coord_check(double value) {
192   assert(-10 < value && value < 10);
193   return value;
194 }
195
196 static void compute_outvertices(void) {
197   int v0,k,side,ab,x,y;
198
199   FOR_VERTEX(v0) {
200     for (ab=0; ab<2; ab++) {
201       int v1= EDGE_END2(v0, ab?5:0);
202       int v2= EDGE_END2(v0, ab?0:1);
203       if (v1<0 || v2<0)
204         continue;
205       double normal[D3], centroid[D3];
206       triangle_normal(normal, in[v0],in[v1],in[v2]);
207       normalise_thick(normal);
208       K centroid[k]= (in[v0][k] + in[v1][k] + in[v2][k]) / 3.0;
209       K Ok(ovAB[v0][ab][0], centroid[k] + normal[k]);
210       K Ok(ovAB[v0][ab][1], centroid[k] - normal[k]);
211     }
212   }
213   FOR_VERTEX(v0) {
214     int vw= EDGE_END2(v0,3);
215     int vnw= EDGE_END2(v0,2);
216     int vsw= EDGE_END2(v0,4);
217     if (vnw<0 || vsw<0 || vw<0)
218       continue;
219     FOR_SIDE {
220       double adjust[D3];
221       int e;
222       K adjust[k]= 0;
223       FOR_VPEDGE(e) {
224         OutVertex *ovab= invertex2outvertexab(v0,e,side);
225         K {
226           assert(!isnan(ovab->p[k]));
227           adjust[k] += ovab->p[k];
228         }
229       }
230       K adjust[k] /= 6;
231       K adjust[k] -= in[v0][k];
232       normalise_thick(adjust);
233       K Ok(ovC[v0][side], in[v0][k] + adjust[k]);
234     }
235   }
236   FOR_RIM_VERTEX(y,x,v0) {
237     double rim[D3], inner[D3], radius_cos[D3], radius_sin[D3];
238     int vback, vfwd, around;
239
240     /* compute mean rim vector, which is just the vector between
241      * the two adjacent rim vertex (ignoring the base vertex) */
242     vback= EDGE_END2(v0,3);
243     vfwd=  EDGE_END2(v0,0);
244     assert(vback>=0 && vfwd>=0);
245     K rim[k]= in[vfwd][k] - in[vback][k];
246
247     /* compute the inner centroid */
248     vback= EDGE_END2(v0,4);
249     if (vback>=0) { /* North rim */
250       vfwd= EDGE_END2(v0,5);
251     } else { /* South rim */
252       vback= EDGE_END2(v0,2);
253       vfwd=  EDGE_END2(v0,1);
254     }
255     assert(vback>=0 && vfwd>=0);
256     K inner[k]= (in[vback][k] + in[vfwd][k]) / 2;
257     K inner[k] -= in[v0][k];
258
259     /* we compute the radius cos and sin vectors by cross producting
260      * the vector to the inner with the rim, and then again, and
261      * normalising. */
262     xprod(radius_cos,rim,inner);
263     xprod(radius_sin,rim,radius_cos);
264     normalise_thick(radius_cos);
265     normalise_thick(radius_sin);
266
267     int_map *around_map= y ? int_identity_function : defs_aroundmap_swap;
268
269     for (around=0; around<NDEF; around++) {
270       double angle= around_map(around) * M_PI / (NDEF-1);
271       K Ok(ovDEF[x][!!y][around],
272            in[v0][k] +
273            cos(angle) * radius_cos[k] +
274            sin(angle) * radius_sin[k]);
275     }
276   }
277   FOR_RIM_VERTEX(y,x,v0) {
278     int vfwd= EDGE_END2(v0,0);
279     assert(vfwd >= 0);
280     int aroung;
281     int_map *around_map= vertices_span_join_p(v0,vfwd)
282       ? defs_aroundmap_swap : int_identity_function;
283     for (aroung=0; aroung<NG; aroung++) {
284       K Ok(ovG[x][!!y][aroung],
285            0.5 * (ovDEF[ x          ][!! y             ][aroung*2+1].p[k] +
286                   ovDEF[vfwd & XMASK][!!(vfwd & ~XMASK)][around_map(aroung*2+1)].p[k]));
287     }
288   }
289 }
290
291 /*---------- output facets ----------*/
292
293 static void outfacets_around(int reverse, OutVertex *middle,
294                              int nsurr, OutVertex *surround[nsurr]) {
295   /* Some entries in surround may be 0, in which case all affected
296    *  facets will be skipped */
297   int i;
298   for (i=0; i<nsurr; i++) {
299     OutVertex *s0= surround[i];
300     OutVertex *s1= surround[(i+1) % nsurr];
301     if (!s0 || !s1) continue;
302     outfacet(reverse, middle,s0,s1);
303   }
304 }
305
306 static OutVertex *invertex2outvertexcd(v0,side) {
307   if (!RIM_VERTEX_P(v0)) return &ovC[v0][side];
308
309   int around= side ? NDEF-1 : 0;
310   int rimy= !!(v0 & ~XMASK);
311   return &ovDEF[v0 & XMASK][rimy][around];
312 }
313
314 static void outfacets(void) {
315   int v0,e,side,aroung;
316   
317   FOR_VERTEX(v0) {
318     OutVertex *defs=0, *defs1=0;
319     int rimy=-1;
320     int_map *defs1aroundmap= 0;
321     if (RIM_VERTEX_P(v0)) {
322       OutVertex *gs;
323       rimy= !!(v0 & ~XMASK);
324       int v1= EDGE_END2(v0,0);  assert(v1>=0);
325       gs=    ovG  [v0 & XMASK][rimy];
326       defs=  ovDEF[v0 & XMASK][rimy];
327       defs1= ovDEF[v1 & XMASK][!!(v1 & ~XMASK)];
328       defs1aroundmap= vertices_span_join_p(v0,v1)
329         ? defs_aroundmap_swap : int_identity_function;
330
331       for (aroung=0; aroung<NG; aroung++) {
332         int around= aroung*2;
333         OutVertex *surround[6];
334         for (e=0; e<3; e++) {
335           surround[e  ]= &defs1[defs1aroundmap(around  +e)];
336           surround[e+3]= &defs [               around+2-e ];
337         }
338         outfacets_around(rimy, &gs[aroung], 6,surround);
339       }
340     }
341
342     FOR_SIDE {
343       int ab;
344       for (ab=0; ab<2; ab++) {
345         int v1= EDGE_END2(v0, ab ? 5 : 0);
346         int v2= EDGE_END2(v0, ab ? 0 : 1);
347         if (v1<0 || v2<0) continue;
348         outfacet(side,
349                  invertex2outvertexcd(v0,side),
350                  invertex2outvertexcd(v1,side^vertices_span_join_p(v0,v1)),
351                  invertex2outvertexcd(v2,side^vertices_span_join_p(v0,v2)));
352       }
353     }
354   }
355 }     
356
357 /*---------- operations on very output vertex ----------*/
358
359 #define DO_OUTPUT_ARRAY_OUTVERTEX_ARRAY(fn,ovX) \
360   ((fn)(sizeof((ovX))/sizeof(OutVertex), (OutVertex*)(ovX)))
361
362 static void blank_outvertex_array(int n, OutVertex ovX[n]) {
363   int i, k;
364   for (i=0; i<n; i++)
365     K ovX[i].p[k]= NAN;
366 }
367
368 static void blank_outvertices(void) {
369 #define BLANK_OUTPUT_ARRAY(ovX) \
370       DO_OUTPUT_ARRAY_OUTVERTEX_ARRAY(blank_outvertex_array, ovX);
371   OUTPUT_ARRAY_LIST(BLANK_OUTPUT_ARRAY)
372 }
373
374 static void transform_outvertex_array(int n, OutVertex ovX[n]) {
375   int i, k;
376   for (i=0; i<n; i++) {
377     ovX[i].p[0] *= -1;
378     ovX[i].p[1] *= -1;
379     K ovX[i].p[k] *= scale;
380   }
381   /*
382    *   double min[D3]= thick;
383    *            if (ovX[i].p[k] < min)
384    *        min= ovX[i].p[k];
385    *      for (i=0; i<n; i++) {
386    *  K ovX[k].p[k] -= min;
387    */
388 }
389
390 static void transform_outvertices(void) {
391 #define TRANSFORM_OUTPUT_ARRAY(ovX) \
392       DO_OUTPUT_ARRAY_OUTVERTEX_ARRAY(transform_outvertex_array, ovX);
393   OUTPUT_ARRAY_LIST(TRANSFORM_OUTPUT_ARRAY)
394 }
395
396 /*---------- output file ----------*/
397
398 static void wr(const void *p, size_t sz) {
399   if (fwrite(p,sz,1,stdout) != 1)
400     diee("fwrite");
401 }
402
403 #define WR(x) wr((const void*)&(x), sizeof((x)))
404
405 static void wf(double d) {
406   typedef float ieee754single;
407
408   assert(sizeof(ieee754single)==4);
409   assert(!isnan(d));
410
411 assert(d >= -1e3 && d <= 1e3);
412
413 #if BYTE_ORDER==BIG_ENDIAN
414   union { uint8_t b[4]; ieee754single f; } value;  value.f= d;
415   int i;  for (i=3; i>=0; i--) WR(value.b[i]);
416 #elif BYTE_ORDER==LITTLE_ENDIAN
417   ieee754single f= d;
418   WR(f);
419 #else
420 # error not little or big endian!
421 #endif
422 }
423
424 static uint32_t noutfacets;
425 static uint32_t noutfacets_counted;
426 static long badfacets;
427
428 static void outfacet(int rev, const OutVertex *a,
429                      const OutVertex *b, const OutVertex *c) {
430   if (rev) { outfacet(0, c,b,a); return; }
431
432   double normal[D3];
433   int k;
434   
435   K {
436     assert(!isnan(a->p[k]));
437     assert(!isnan(b->p[k]));
438     assert(!isnan(c->p[k]));
439   }
440
441   triangle_normal(normal, a->p, b->p, c->p);
442   double multby= 1/magnD(normal);
443   
444   if (multby > 1e6) {
445     badfacets++;
446     return;
447   }
448
449   noutfacets++;
450   if (!~noutfacets_counted) return;
451   
452   K normal[k] *= multby;
453
454   K wf(normal[k]);
455   K wf(a->p[k]);
456   K wf(b->p[k]);
457   K wf(c->p[k]);
458   uint16_t attrbc=0;
459   WR(attrbc);
460 }
461
462 static void write_file(void) {
463   static const char header[80]= "#!/usr/bin/meshlab\n" "binary STL file\n";
464
465   if (isatty(1)) fail("will not write binary stl to tty!\n");
466
467   WR(header);
468
469   noutfacets_counted=~(uint32_t)0;
470   noutfacets=0;
471   outfacets();
472   WR(noutfacets);
473   
474   noutfacets_counted= noutfacets;
475   noutfacets=0;
476   outfacets();
477   assert(noutfacets == noutfacets_counted);
478
479   if (fflush(stdout)) diee("fflush stdout");
480
481   if (badfacets) {
482     fprintf(stderr,"%ld degenerate facets!\n",badfacets);
483     exit(4);
484   }
485 }  
486   
487
488 /*---------- main program etc. ----------*/
489
490 int main(int argc, const char *const *argv) {
491   int r;
492
493   if (argc!=3 || argv[1][0]=='-') { fputs("bad usage\n",stderr); exit(8); }
494   thick= atof(argv[1]);
495   scale= atof(argv[2]) * 0.5; /* circle is unit radius but arg is diameter */
496
497   errno= 0; r= fread(&in,sizeof(in),1,stdin);
498   if (r!=1) diee("fread");
499
500   blank_outvertices();
501   compute_outvertices();
502   transform_outvertices();
503   write_file();
504   if (fclose(stdout)) diee("fclose stdout");
505   return 0;
506 }