ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/hd/rhd_odraw.c
Revision: 3.2
Committed: Sun Dec 20 20:37:53 1998 UTC (25 years, 4 months ago) by gwlarson
Content type: text/plain
Branch: MAIN
Changes since 3.1: +68 -27 lines
Log Message:
improved sample insertion to take better samples
fixed and improved tone mapping

File Contents

# Content
1 /* Copyright (c) 1998 Silicon Graphics, Inc. */
2
3 #ifndef lint
4 static char SCCSid[] = "$SunId$ SGI";
5 #endif
6
7 /*
8 * Routines for drawing samples using depth buffer checks.
9 */
10
11 #include "standard.h"
12
13 #include <sys/types.h>
14 #include <GL/glx.h>
15 #include <GL/glu.h>
16
17 #include "random.h"
18 #include "rhd_odraw.h"
19
20 #ifndef DEPTHEPS
21 #define DEPTHEPS 0.02 /* depth epsilon */
22 #endif
23 #ifndef SAMPSPERBLOCK
24 #define SAMPSPERBLOCK 1024 /* target samples per image block */
25 #endif
26 #ifndef SFREEFRAC
27 #define SFREEFRAC 0.2 /* fraction to free at a time */
28 #endif
29 #ifndef MAXFAN
30 #define MAXFAN 32 /* maximum arms in a triangle fan */
31 #endif
32 #ifndef MINFAN
33 #define MINFAN 4 /* minimum arms in a triangle fan */
34 #endif
35 #ifndef FANSIZE
36 #define FANSIZE 3.5 /* fan sizing factor */
37 #endif
38
39 #define NEWMAP 01 /* need to recompute mapping */
40 #define NEWRGB 02 /* need to remap RGB values */
41 #define NEWHIST 04 /* clear histogram as well */
42
43 struct ODview *odView; /* our view list */
44 int odNViews; /* number of views in our list */
45
46 struct ODsamp odS; /* sample values */
47
48 static int needmapping; /* what needs doing with tone map */
49
50
51 #define SAMP32 (32*(2*sizeof(short)+sizeof(union ODfunion)+sizeof(TMbright)+\
52 6*sizeof(BYTE))+sizeof(int4))
53
54 int
55 odInit(n) /* initialize drawing routines */
56 int n;
57 {
58 int nbytes, i, j, k, nextsamp, count, blockdiv;
59 int res[2];
60
61 if (odNViews) { /* deallocate view structures */
62 for (i = 0; i < odNViews; i++) {
63 free((char *)odView[i].bmap);
64 if (odView[i].emap != NULL)
65 free((char *)odView[i].emap);
66 }
67 free((char *)odView);
68 odView = NULL;
69 odNViews = 0;
70 }
71 if (n && n != odS.nsamp) {
72 /* round space up to nearest power of 2 */
73 nbytes = (n+31)/32 * SAMP32;
74 for (i = 1024; nbytes > i-8; i <<= 1)
75 ;
76 n = (i-8)/SAMP32 * 32;
77 needmapping = NEWHIST;
78 }
79 if (n != odS.nsamp) { /* (re)allocate sample array */
80 if (odS.nsamp)
81 free(odS.base);
82 odS.nsamp = 0;
83 if (!n)
84 return(0);
85 nbytes = (n+31)/32 * SAMP32;
86 odS.base = (char *)malloc(nbytes);
87 if (odS.base == NULL)
88 return(0);
89 /* assign larger alignment types earlier */
90 odS.f = (union ODfunion *)odS.base;
91 odS.redraw = (int4 *)(odS.f + n);
92 odS.ip = (short (*)[2])(odS.redraw + n/32);
93 odS.brt = (TMbright *)(odS.ip + n);
94 odS.chr = (BYTE (*)[3])(odS.brt + n);
95 odS.rgb = (BYTE (*)[3])(odS.chr + n);
96 odS.nsamp = n;
97 }
98 if (!n)
99 return(0);
100 /* allocate view information */
101 count = 0; /* count pixels */
102 for (i = 0; dev_auxview(i, res) != NULL; i++)
103 count += res[0]*res[1];
104 odView = (struct ODview *)malloc(i*sizeof(struct ODview));
105 if (odView == NULL)
106 return(0);
107 odNViews = i;
108 blockdiv = sqrt(count/(n/SAMPSPERBLOCK)) + 0.5;
109 if (blockdiv < 8) blockdiv = 8;
110 nextsamp = 0; count /= blockdiv*blockdiv; /* # blocks */
111 while (i--) { /* initialize each view */
112 odView[i].emap = NULL;
113 odView[i].dmap = NULL;
114 dev_auxview(i, res);
115 odView[i].hhi = res[0];
116 odView[i].hlow = (res[0] + blockdiv/2) / blockdiv;
117 if (odView[i].hlow < 1) odView[i].hlow = 1;
118 odView[i].vhi = res[1];
119 odView[i].vlow = (res[1] + blockdiv/2) / blockdiv;
120 if (odView[i].vlow < 1) odView[i].vlow = 1;
121 j = odView[i].hlow*odView[i].vlow;
122 odView[i].bmap = (struct ODblock *)malloc(
123 j * sizeof(struct ODblock));
124 if (odView[i].bmap == NULL)
125 return(0);
126 DCHECK(count<=0 | nextsamp>=n,
127 CONSISTENCY, "counter botch in odInit");
128 if (!i) count = j;
129 while (j--) { /* initialize blocks & free lists */
130 odView[i].bmap[j].pthresh = FHUGE;
131 odView[i].bmap[j].first = k = nextsamp;
132 nextsamp += odView[i].bmap[j].nsamp =
133 (n - nextsamp)/count--;
134 odView[i].bmap[j].free = k;
135 while (++k < nextsamp)
136 odS.nextfree(k-1) = k;
137 odS.nextfree(k-1) = ENDFREE;
138 odView[i].bmap[j].nused = 0;
139 }
140 }
141 CLR4ALL(odS.redraw, odS.nsamp); /* clear redraw flags */
142 for (i = odS.nsamp; i--; ) { /* clear values */
143 odS.ip[i][0] = odS.ip[i][1] = -1;
144 odS.brt[i] = TM_NOBRT;
145 }
146 needmapping |= NEWMAP; /* compute new map on update */
147 return(odS.nsamp); /* return number of samples */
148 }
149
150 #undef SAMP32
151
152
153 int
154 sampcmp(s0, s1) /* sample order, descending proximity */
155 int *s0, *s1;
156 {
157 register double diff = odS.closeness(*s1) - odS.closeness(*s0);
158
159 return (diff > FTINY ? 1 : diff < -FTINY ? -1 : 0);
160 }
161
162
163 int
164 odAllocBlockSamp(vn, hh, vh, prox) /* allocate sample from block */
165 int vn, hh, vh;
166 double prox;
167 {
168 int si[SAMPSPERBLOCK+SAMPSPERBLOCK/4];
169 int hl, vl;
170 VIEW *vw;
171 FVECT ro, rd;
172 int res[2];
173 register struct ODblock *bp;
174 register int i, j;
175 /* get block */
176 hl = hh*odView[vn].hlow/odView[vn].hhi;
177 vl = vh*odView[vn].vlow/odView[vn].vhi;
178 bp = odView[vn].bmap + vl*odView[vn].hlow + hl;
179 if (prox > bp->pthresh)
180 return(-1); /* worse than free list occupants */
181 /* check for duplicate pixel */
182 for (i = bp->first+bp->nsamp; i-- > bp->first; )
183 if (hh == odS.ip[i][0] && vh == odS.ip[i][1]) { /* found it! */
184 /* search free list for it */
185 if (i == bp->free)
186 break; /* special case */
187 if (bp->free != ENDFREE)
188 for (j = bp->free; odS.nextfree(j) != ENDFREE;
189 j = odS.nextfree(j))
190 if (odS.nextfree(j) == i) {
191 odS.nextfree(j) =
192 odS.nextfree(i);
193 bp->nused++;
194 goto gotit;
195 }
196 if (prox >= 0.999*odS.closeness(i))
197 return(-1); /* previous sample is fine */
198 goto gotit;
199 }
200 if (bp->free != ENDFREE) { /* allocate from free list */
201 i = bp->free;
202 bp->free = odS.nextfree(i);
203 bp->nused++;
204 goto gotit;
205 }
206 DCHECK(bp->nsamp<=0, CONSISTENCY,
207 "no available samples in odAllocBlockSamp");
208 DCHECK(bp->nsamp > sizeof(si)/sizeof(si[0]), CONSISTENCY,
209 "too many samples in odAllocBlockSamp");
210 /* free some samples */
211 if ((vw = dev_auxview(vn, res)) == NULL)
212 error(CONSISTENCY, "bad view number in odAllocBlockSamp");
213 for (i = bp->nsamp; i--; ) /* figure out which are worse */
214 si[i] = bp->first + i;
215 qsort((char *)si, bp->nsamp, sizeof(int), sampcmp);
216 i = bp->nsamp*SFREEFRAC + .5; /* put them into free list */
217 if (i >= bp->nsamp) i = bp->nsamp-1; /* paranoia */
218 bp->pthresh = odS.closeness(si[i]); /* new proximity threshold */
219 while (--i > 0) {
220 odS.nextfree(si[i]) = bp->free;
221 bp->free = si[i];
222 bp->nused--;
223 }
224 i = si[0]; /* use worst sample */
225 gotit:
226 odS.ip[i][0] = hh;
227 odS.ip[i][1] = vh;
228 odS.closeness(i) = prox;
229 return(i);
230 }
231
232
233 odSample(c, d, p) /* add a sample value */
234 COLR c;
235 FVECT d, p;
236 {
237 FVECT disp;
238 double d0, d1, h, v, prox;
239 register VIEW *vw;
240 int hh, vh;
241 int res[2];
242 register int i, id;
243
244 DCHECK(odS.nsamp<=0, CONSISTENCY, "no samples allocated in odSample");
245 /* add value to each view */
246 for (i = 0; (vw = dev_auxview(i, res)) != NULL; i++) {
247 DCHECK(i>=odNViews, CONSISTENCY, "too many views in odSample");
248 CHECK(vw->type!=VT_PER, INTERNAL,
249 "cannot handle non-perspective views");
250 if (p != NULL) { /* compute view position */
251 VSUB(disp, p, vw->vp);
252 d0 = DOT(disp, vw->vdir);
253 if (d0 <= vw->vfore+FTINY)
254 continue; /* too close */
255 } else {
256 VCOPY(disp, d);
257 d0 = DOT(disp, vw->vdir);
258 if (d0 <= FTINY) /* behind view */
259 continue;
260 }
261 h = DOT(disp,vw->hvec)/(d0*vw->hn2) + 0.5 - vw->hoff;
262 if (h < 0. || h >= 1.)
263 continue; /* left or right */
264 v = DOT(disp,vw->vvec)/(d0*vw->vn2) + 0.5 - vw->voff;
265 if (v < 0. || v >= 1.)
266 continue; /* above or below */
267 hh = h * res[0];
268 vh = v * res[1];
269 if (odView[i].dmap != NULL) { /* check depth */
270 d1 = odView[i].dmap[vh*res[0] + hh];
271 if (d1 < 0.99*FHUGE && (d0 > (1.+DEPTHEPS)*d1 ||
272 (1.+DEPTHEPS)*d0 < d1))
273 continue; /* occlusion error */
274 }
275 if (p != NULL) { /* compute closeness (sin^2) */
276 d1 = DOT(disp, d);
277 prox = 1. - d1*d1/DOT(disp,disp);
278 } else
279 prox = 0.;
280 /* allocate sample */
281 id = odAllocBlockSamp(i, hh, vh, prox);
282 if (id < 0)
283 continue; /* not good enough */
284 /* convert color */
285 tmCvColrs(&odS.brt[id], odS.chr[id], c, 1);
286 if (imm_mode | needmapping) /* if immediate mode */
287 needmapping |= NEWRGB; /* map it later */
288 else /* else map it now */
289 tmMapPixels(odS.rgb[id], &odS.brt[id], odS.chr[id], 1);
290 SET4(odS.redraw, id); /* mark for redraw */
291 }
292 }
293
294
295 odRemap(newhist) /* recompute tone mapping */
296 int newhist;
297 {
298 needmapping |= NEWMAP|NEWRGB;
299 if (newhist)
300 needmapping |= NEWHIST;
301 }
302
303
304 odRedraw(vn, hmin, vmin, hmax, vmax) /* redraw view region */
305 int vn, hmin, vmin, hmax, vmax;
306 {
307 int i, j;
308 register struct ODblock *bp;
309 register int k;
310
311 if (vn<0 | vn>=odNViews)
312 return;
313 /* check view limits */
314 if (hmin < 0) hmin = 0;
315 if (hmax >= odView[vn].hhi) hmax = odView[vn].hhi-1;
316 if (vmin < 0) vmin = 0;
317 if (vmax >= odView[vn].vhi) vmax = odView[vn].vhi-1;
318 if (hmax <= hmin | vmax <= vmin)
319 return;
320 /* convert to low resolution */
321 hmin = hmin * odView[vn].hlow / odView[vn].hhi;
322 hmax = hmax * odView[vn].hlow / odView[vn].hhi;
323 vmin = vmin * odView[vn].vlow / odView[vn].vhi;
324 vmax = vmax * odView[vn].vlow / odView[vn].vhi;
325 /* mark block samples for redraw, inclusive */
326 for (i = hmin; i <= hmax; i++)
327 for (j = vmin; j <= vmax; j++) {
328 bp = odView[vn].bmap + j*odView[vn].hlow + i;
329 for (k = bp->nsamp; k--; )
330 if (odS.ip[bp->first+k][0] >= 0)
331 SET4(odS.redraw, bp->first+k);
332 }
333 }
334
335
336 odDepthMap(vn, dm) /* assign depth map for view */
337 int vn;
338 GLfloat *dm;
339 {
340 double d0, d1;
341 int i, j, hmin, hmax, vmin, vmax;
342 register int k, l;
343
344 if (dm == NULL) { /* free edge map */
345 if (vn<0 | vn>=odNViews)
346 return; /* too late -- they're gone! */
347 if (odView[vn].emap != NULL)
348 free((char *)odView[vn].emap);
349 odView[vn].emap = NULL;
350 odView[vn].dmap = NULL;
351 return;
352 }
353 DCHECK(vn<0 | vn>=odNViews, CONSISTENCY,
354 "bad view number in odDepthMap");
355 odView[vn].dmap = dm; /* initialize edge map */
356 if (odView[vn].emap == NULL) {
357 odView[vn].emap = (int4 *)malloc(
358 FL4NELS(odView[vn].hlow*odView[vn].vlow)*sizeof(int4));
359 if (odView[vn].emap == NULL)
360 error(SYSTEM, "out of memory in odDepthMap");
361 }
362 CLR4ALL(odView[vn].emap, odView[vn].hlow*odView[vn].vlow);
363 /* compute edge map */
364 vmin = odView[vn].vhi; /* enter loopsville */
365 for (j = odView[vn].vlow; j--; ) {
366 vmax = vmin;
367 vmin = j*odView[vn].vhi/odView[vn].vlow;
368 hmin = odView[vn].hhi;
369 for (i = odView[vn].hlow; i--; ) {
370 hmax = hmin;
371 hmin = i*odView[vn].hhi/odView[vn].hlow;
372 for (l = vmin; l < vmax; l++) { /* vertical edges */
373 d1 = dm[l*odView[vn].hhi+hmin];
374 for (k = hmin+1; k < hmax; k++) {
375 d0 = d1;
376 d1 = dm[l*odView[vn].hhi+k];
377 if (d0 > (1.+DEPTHEPS)*d1 ||
378 (1.+DEPTHEPS)*d0 < d1) {
379 SET4(odView[vn].emap,
380 j*odView[vn].hlow + i);
381 break;
382 }
383 }
384 if (k < hmax)
385 break;
386 }
387 if (l < vmax)
388 continue;
389 for (k = hmin; k < hmax; k++) { /* horizontal edges */
390 d1 = dm[vmin*odView[vn].hhi+k];
391 for (l = vmin+1; l < vmax; l++) {
392 d0 = d1;
393 d1 = dm[l*odView[vn].hhi+k];
394 if (d0 > (1.+DEPTHEPS)*d1 ||
395 (1.+DEPTHEPS)*d0 < d1) {
396 SET4(odView[vn].emap,
397 j*odView[vn].hlow + i);
398 break;
399 }
400 }
401 if (l < vmax)
402 break;
403 }
404 }
405 }
406 }
407
408
409 odUpdate(vn) /* update this view */
410 int vn;
411 {
412 int i, j;
413 register struct ODblock *bp;
414 register int k;
415
416 DCHECK(vn<0 | vn>=odNViews, CONSISTENCY,
417 "bad view number in odUpdate");
418 /* need to do some tone mapping? */
419 if (needmapping & NEWRGB) {
420 if (needmapping & NEWMAP) {
421 if (needmapping & NEWHIST)
422 tmClearHisto();
423 if (tmAddHisto(odS.brt,odS.nsamp,1) != TM_E_OK)
424 return;
425 if (tmComputeMapping(0.,0.,0.) != TM_E_OK)
426 return;
427 for (k = odS.nsamp; k--; ) /* redraw all */
428 if (odS.ip[k][0] >= 0)
429 SET4(odS.redraw, k);
430 }
431 if (tmMapPixels(odS.rgb,odS.brt,odS.chr,odS.nsamp) != TM_E_OK)
432 return;
433 needmapping = 0; /* reset flag */
434 }
435 /* draw each block in view */
436 for (j = odView[vn].vlow; j--; )
437 for (i = 0; i < odView[vn].hlow; i++) {
438 /* get block */
439 bp = odView[vn].bmap + j*odView[vn].hlow + i;
440 /* do quick, conservative flag check */
441 for (k = (bp->first+bp->nsamp+31)>>5;
442 k-- > bp->first>>5; )
443 if (odS.redraw[k])
444 break; /* non-zero flag */
445 if (k < bp->first>>5)
446 continue; /* no flags set */
447 for (k = bp->nsamp; k--; ) /* sample by sample */
448 if (CHK4(odS.redraw, bp->first+k)) {
449 odDrawBlockSamp(vn, i, j, bp->first+k);
450 CLR4(odS.redraw, bp->first+k);
451 }
452 }
453 }
454
455
456 #if 0
457 static
458 clip_end(p, o, vp) /* clip line segment to view */
459 GLshort p[3];
460 short o[2];
461 register struct ODview *vp;
462 {
463 if (p[0] < 0) {
464 p[1] = -o[0]*(p[1]-o[1])/(p[0]-o[0]) + o[1];
465 p[2] = -o[0]*p[2]/(p[0]-o[0]);
466 p[0] = 0;
467 } else if (p[0] >= vp->hhi) {
468 p[1] = (vp->hhi-1-o[0])*(p[1]-o[1])/(p[0]-o[0]) + o[1];
469 p[2] = (vp->hhi-1-o[0])*p[2]/(p[0]-o[0]);
470 p[0] = vp->hhi-1;
471 }
472 if (p[1] < 0) {
473 p[0] = -o[1]*(p[0]-o[0])/(p[1]-o[1]) + o[0];
474 p[2] = -o[1]*p[2]/(p[1]-o[1]);
475 p[1] = 0;
476 } else if (p[1] >= vp->vhi) {
477 p[0] = (vp->vhi-1-o[1])*(p[0]-o[0])/(p[1]-o[1]) + o[0];
478 p[2] = (vp->vhi-1-o[1])*p[2]/(p[1]-o[1]);
479 p[1] = vp->vhi-1;
480 }
481 }
482 #endif
483
484
485 static int
486 make_arms(ar, cp, vp, sz) /* make arms for triangle fan */
487 GLshort ar[MAXFAN][3];
488 short cp[2];
489 register struct ODview *vp;
490 double sz;
491 {
492 int na, dv;
493 double hrad, vrad, phi0, phi;
494 register int i;
495
496 DCHECK(sz > 1, CONSISTENCY, "super-unary size in make_arms");
497 na = MAXFAN*sz*sz + 0.5; /* keep area constant */
498 if (na < MINFAN) na = MINFAN;
499 hrad = FANSIZE*sz*vp->hhi/vp->hlow;
500 vrad = FANSIZE*sz*vp->vhi/vp->vlow;
501 if (hrad*vrad < 2.25)
502 hrad = vrad = 1.5;
503 phi0 = (2.*PI) * frandom();
504 dv = OMAXDEPTH*sz + 0.5;
505 for (i = 0; i < na; i++) {
506 phi = phi0 + (2.*PI)*i/na;
507 ar[i][0] = cp[0] + tcos(phi)*hrad + 0.5;
508 ar[i][1] = cp[1] + tsin(phi)*vrad + 0.5;
509 ar[i][2] = dv;
510 /* clip_end(ar[i], cp, vp); */
511 }
512 return(na);
513 }
514
515
516 static int
517 depthchange(vp, x0, y0, x1, y1) /* check depth discontinuity */
518 register struct ODview *vp;
519 int x0, y0, x1, y1;
520 {
521 register double d0, d1;
522
523 DCHECK(x0<0 | x0>=vp->hhi | y0<0 | y0>=vp->vhi,
524 CONSISTENCY, "coordinates off view in depthchange");
525
526 if (x1<0 | x1>=vp->hhi | y1<0 | y1>=vp->vhi)
527 return(1);
528
529 d0 = vp->dmap[y0*vp->hhi + x0];
530 d1 = vp->dmap[y1*vp->hhi + x1];
531
532 return((1.+DEPTHEPS)*d0 < d1 || d0 > (1.+DEPTHEPS)*d1);
533 }
534
535
536 static
537 clip_edge(p, o, vp) /* clip line segment to depth edge */
538 GLshort p[3];
539 short o[2];
540 register struct ODview *vp;
541 {
542 int x, y, xstep, ystep, rise, rise2, run, run2, n;
543
544 DCHECK(vp->dmap==NULL, CONSISTENCY,
545 "clip_edge called with no depth map");
546 x = o[0]; y = o[1];
547 run = p[0] - x;
548 xstep = run > 0 ? 1 : -1;
549 run *= xstep;
550 rise = p[1] - y;
551 ystep = rise > 0 ? 1 : -1;
552 rise *= ystep;
553 rise2 = run2 = 0;
554 if (rise > run) rise2 = 1;
555 else run2 = 1;
556 n = rise + run;
557 while (n--) /* run out arm, checking depth */
558 if (run2 > rise2) {
559 if (depthchange(vp, x, y, x+xstep, y))
560 break;
561 x += xstep;
562 rise2 += rise;
563 } else {
564 if (depthchange(vp, x, y, x, y+ystep))
565 break;
566 y += ystep;
567 run2 += run;
568 }
569 if (n < 0) /* found something? */
570 return;
571 if (run > rise)
572 p[2] = (x - o[0])*p[2]/(p[0] - o[0]);
573 else
574 p[2] = (y - o[1])*p[2]/(p[1] - o[1]);
575 p[0] = x;
576 p[1] = y;
577 }
578
579
580 static int
581 getblock(vp, h, v) /* get block index */
582 register struct ODview *vp;
583 register int h, v;
584 {
585 if (h<0 | h>=vp->hhi | v<0 | v>=vp->vhi)
586 return(-1);
587 return(h*vp->hlow/vp->hhi + v*vp->vlow/vp->vhi*vp->hlow);
588 }
589
590
591 odDrawBlockSamp(vn, h, v, id) /* draw sample in view block */
592 int vn, h, v;
593 register int id;
594 {
595 GLshort arm[MAXFAN][3];
596 int narms, blockindex, bi1;
597 register struct ODview *vp;
598 double size;
599 int home_edges;
600 register int i;
601
602 vp = odView + vn;
603 blockindex = v*vp->hlow + h;
604 DCHECK(odS.ip[id][0]*vp->hlow/vp->hhi != h |
605 odS.ip[id][1]*vp->vlow/vp->vhi != v,
606 CONSISTENCY, "bad sample position in odDrawBlockSamp");
607 DCHECK(vp->bmap[blockindex].nused <= 0,
608 CONSISTENCY, "bad in-use count in odDrawBlockSamp");
609 /* create triangle fan */
610 size = 1./sqrt((double)vp->bmap[blockindex].nused);
611 narms = make_arms(arm, odS.ip[id], vp, size);
612 if (vp->emap != NULL) { /* check for edge collisions */
613 home_edges = CHK4(vp->emap, blockindex);
614 for (i = 0; i < narms; i++)
615 /* the following test is flawed, because we could
616 * be passing through a block on a diagonal run */
617 if (home_edges ||
618 ( (bi1 = getblock(vp, arm[i][0], arm[i][1]))
619 != blockindex &&
620 (bi1 < 0 || CHK4(vp->emap, bi1)) ))
621 clip_edge(arm[i], odS.ip[id], vp);
622 }
623 /* draw triangle fan */
624 glColor3ub(odS.rgb[id][0], odS.rgb[id][1], odS.rgb[id][2]);
625 glBegin(GL_TRIANGLE_FAN);
626 glVertex3s((GLshort)odS.ip[id][0], (GLshort)odS.ip[id][1], (GLshort)0);
627 for (i = 0; i < narms; i++)
628 glVertex3sv(arm[i]);
629 glVertex3sv(arm[0]); /* connect last to first */
630 glEnd();
631 }