ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/rt/source.c
Revision: 2.59
Committed: Thu Oct 28 09:38:46 2010 UTC (13 years, 6 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.58: +27 -25 lines
Log Message:
Fixed Windows bug in mkillum by initializing rmax

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: source.c,v 2.58 2010/08/23 15:56:17 greg Exp $";
3 #endif
4 /*
5 * source.c - routines dealing with illumination sources.
6 *
7 * External symbols declared in source.h
8 */
9
10 #include "ray.h"
11 #include "otypes.h"
12 #include "rtotypes.h"
13 #include "source.h"
14 #include "random.h"
15
16 extern double ssampdist; /* scatter sampling distance */
17
18 #ifndef MAXSSAMP
19 #define MAXSSAMP 16 /* maximum samples per ray */
20 #endif
21
22 /*
23 * Structures used by direct()
24 */
25
26 typedef struct {
27 int sno; /* source number */
28 FVECT dir; /* source direction */
29 COLOR coef; /* material coefficient */
30 COLOR val; /* contribution */
31 } CONTRIB; /* direct contribution */
32
33 typedef struct {
34 int sndx; /* source index (to CONTRIB array) */
35 float brt; /* brightness (for comparison) */
36 } CNTPTR; /* contribution pointer */
37
38 static CONTRIB *srccnt; /* source contributions in direct() */
39 static CNTPTR *cntord; /* source ordering in direct() */
40 static int maxcntr = 0; /* size of contribution arrays */
41
42 static int cntcmp(const void *p1, const void *p2);
43
44
45 extern OBJREC * /* find an object's actual material */
46 findmaterial(register OBJREC *o)
47 {
48 while (!ismaterial(o->otype)) {
49 if (o->otype == MOD_ALIAS && o->oargs.nsargs) {
50 OBJECT aobj;
51 OBJREC *ao;
52 aobj = lastmod(objndx(o), o->oargs.sarg[0]);
53 if (aobj < 0)
54 objerror(o, USER, "bad reference");
55 ao = objptr(aobj);
56 if (ismaterial(ao->otype))
57 return(ao);
58 if (ao->otype == MOD_ALIAS) {
59 o = ao;
60 continue;
61 }
62 }
63 if (o->omod == OVOID)
64 return(NULL);
65 o = objptr(o->omod);
66 }
67 return(o); /* mixtures will return NULL */
68 }
69
70
71 extern void
72 marksources(void) /* find and mark source objects */
73 {
74 int foundsource = 0;
75 int i;
76 register OBJREC *o, *m;
77 register int ns;
78 /* initialize dispatch table */
79 initstypes();
80 /* find direct sources */
81 for (i = 0; i < nsceneobjs; i++) {
82
83 o = objptr(i);
84
85 if (!issurface(o->otype) || o->omod == OVOID)
86 continue;
87 /* find material */
88 m = findmaterial(objptr(o->omod));
89 if (m == NULL)
90 continue;
91 if (m->otype == MAT_CLIP) {
92 markclip(m); /* special case for antimatter */
93 continue;
94 }
95 if (!islight(m->otype))
96 continue; /* not source modifier */
97
98 if (m->oargs.nfargs != (m->otype == MAT_GLOW ? 4 :
99 m->otype == MAT_SPOT ? 7 : 3))
100 objerror(m, USER, "bad # arguments");
101
102 if (m->otype == MAT_GLOW &&
103 o->otype != OBJ_SOURCE &&
104 m->oargs.farg[3] <= FTINY)
105 continue; /* don't bother */
106 if (m->oargs.farg[0] <= FTINY && m->oargs.farg[1] <= FTINY &&
107 m->oargs.farg[2] <= FTINY)
108 continue; /* don't bother */
109
110 if (sfun[o->otype].of == NULL ||
111 sfun[o->otype].of->setsrc == NULL)
112 objerror(o, USER, "illegal material");
113
114 if ((ns = newsource()) < 0)
115 goto memerr;
116
117 setsource(&source[ns], o);
118
119 if (m->otype == MAT_GLOW) {
120 source[ns].sflags |= SPROX;
121 source[ns].sl.prox = m->oargs.farg[3];
122 if (source[ns].sflags & SDISTANT)
123 source[ns].sflags |= SSKIP;
124 } else if (m->otype == MAT_SPOT) {
125 source[ns].sflags |= SSPOT;
126 if ((source[ns].sl.s = makespot(m)) == NULL)
127 goto memerr;
128 if (source[ns].sflags & SFLAT &&
129 !checkspot(source[ns].sl.s,source[ns].snorm)) {
130 objerror(o, WARNING,
131 "invalid spotlight direction");
132 source[ns].sflags |= SSKIP;
133 }
134 }
135 #if SHADCACHE
136 initobscache(ns);
137 #endif
138 if (!(source[ns].sflags & SSKIP))
139 foundsource++;
140 }
141 if (!foundsource) {
142 error(WARNING, "no light sources found");
143 return;
144 }
145 markvirtuals(); /* find and add virtual sources */
146 /* allocate our contribution arrays */
147 maxcntr = nsources + MAXSPART; /* start with this many */
148 srccnt = (CONTRIB *)malloc(maxcntr*sizeof(CONTRIB));
149 cntord = (CNTPTR *)malloc(maxcntr*sizeof(CNTPTR));
150 if ((srccnt == NULL) | (cntord == NULL))
151 goto memerr;
152 return;
153 memerr:
154 error(SYSTEM, "out of memory in marksources");
155 }
156
157
158 extern void
159 freesources(void) /* free all source structures */
160 {
161 if (nsources > 0) {
162 #if SHADCACHE
163 while (nsources--)
164 freeobscache(&source[nsources]);
165 #endif
166 free((void *)source);
167 source = NULL;
168 nsources = 0;
169 }
170 if (maxcntr <= 0)
171 return;
172 free((void *)srccnt);
173 srccnt = NULL;
174 free((void *)cntord);
175 cntord = NULL;
176 maxcntr = 0;
177 }
178
179
180 extern int
181 srcray( /* send a ray to a source, return domega */
182 register RAY *sr, /* returned source ray */
183 RAY *r, /* ray which hit object */
184 SRCINDEX *si /* source sample index */
185 )
186 {
187 double d; /* distance to source */
188 register SRCREC *srcp;
189
190 rayorigin(sr, SHADOW, r, NULL); /* ignore limits */
191
192 if (r == NULL)
193 sr->rmax = 0.0;
194
195 while ((d = nextssamp(sr, si)) != 0.0) {
196 sr->rsrc = si->sn; /* remember source */
197 srcp = source + si->sn;
198 if (srcp->sflags & SDISTANT) {
199 if (srcp->sflags & SSPOT && spotout(sr, srcp->sl.s))
200 continue;
201 return(1); /* sample OK */
202 }
203 /* local source */
204 /* check proximity */
205 if (srcp->sflags & SPROX && d > srcp->sl.prox)
206 continue;
207 /* check angle */
208 if (srcp->sflags & SSPOT) {
209 if (spotout(sr, srcp->sl.s))
210 continue;
211 /* adjust solid angle */
212 si->dom *= d*d;
213 d += srcp->sl.s->flen;
214 si->dom /= d*d;
215 }
216 return(1); /* sample OK */
217 }
218 return(0); /* no more samples */
219 }
220
221
222 extern void
223 srcvalue( /* punch ray to source and compute value */
224 register RAY *r
225 )
226 {
227 register SRCREC *sp;
228
229 sp = &source[r->rsrc];
230 if (sp->sflags & SVIRTUAL) { /* virtual source */
231 /* check intersection */
232 if (!(*ofun[sp->so->otype].funp)(sp->so, r))
233 return;
234 if (!rayshade(r, r->ro->omod)) /* compute contribution */
235 goto nomat;
236 rayparticipate(r);
237 return;
238 }
239 /* compute intersection */
240 if (sp->sflags & SDISTANT ? sourcehit(r) :
241 (*ofun[sp->so->otype].funp)(sp->so, r)) {
242 if (sp->sa.success >= 0)
243 sp->sa.success++;
244 if (!rayshade(r, r->ro->omod)) /* compute contribution */
245 goto nomat;
246 rayparticipate(r);
247 return;
248 }
249 /* we missed our mark! */
250 if (sp->sa.success < 0)
251 return; /* bitched already */
252 sp->sa.success -= AIMREQT;
253 if (sp->sa.success >= 0)
254 return; /* leniency */
255 sprintf(errmsg, "aiming failure for light source \"%s\"",
256 sp->so->oname);
257 error(WARNING, errmsg); /* issue warning */
258 return;
259 nomat:
260 objerror(r->ro, USER, "material not found");
261 }
262
263
264 static int
265 transillum( /* check if material is transparent illum */
266 OBJECT obj
267 )
268 {
269 OBJREC *m = findmaterial(objptr(obj));
270
271 if (m == NULL)
272 return(1);
273 if (m->otype != MAT_ILLUM)
274 return(0);
275 return(!m->oargs.nsargs || !strcmp(m->oargs.sarg[0], VOIDID));
276 }
277
278
279 extern int
280 sourcehit( /* check to see if ray hit distant source */
281 register RAY *r
282 )
283 {
284 int glowsrc = -1;
285 int transrc = -1;
286 int first, last;
287 register int i;
288
289 if (r->rsrc >= 0) { /* check only one if aimed */
290 first = last = r->rsrc;
291 } else { /* otherwise check all */
292 first = 0; last = nsources-1;
293 }
294 for (i = first; i <= last; i++) {
295 if ((source[i].sflags & (SDISTANT|SVIRTUAL)) != SDISTANT)
296 continue;
297 /*
298 * Check to see if ray is within
299 * solid angle of source.
300 */
301 if (2.*PI*(1. - DOT(source[i].sloc,r->rdir)) > source[i].ss2)
302 continue;
303 /* is it the only possibility? */
304 if (first == last) {
305 r->ro = source[i].so;
306 break;
307 }
308 /*
309 * If it's a glow or transparent illum, just remember it.
310 */
311 if (source[i].sflags & SSKIP) {
312 if (glowsrc < 0)
313 glowsrc = i;
314 continue;
315 }
316 if (transillum(source[i].so->omod)) {
317 if (transrc < 0)
318 transrc = i;
319 continue;
320 }
321 r->ro = source[i].so; /* otherwise, use first hit */
322 break;
323 }
324 /*
325 * Do we need fallback?
326 */
327 if (r->ro == NULL) {
328 if (transrc >= 0 && r->crtype & (AMBIENT|SPECULAR))
329 return(0); /* avoid overcounting */
330 if (glowsrc >= 0)
331 r->ro = source[glowsrc].so;
332 else
333 return(0); /* nothing usable */
334 }
335 /*
336 * Make assignments.
337 */
338 r->robj = objndx(r->ro);
339 for (i = 0; i < 3; i++)
340 r->ron[i] = -r->rdir[i];
341 r->rod = 1.0;
342 r->pert[0] = r->pert[1] = r->pert[2] = 0.0;
343 r->uv[0] = r->uv[1] = 0.0;
344 r->rox = NULL;
345 return(1);
346 }
347
348
349 static int
350 cntcmp( /* contribution compare (descending) */
351 const void *p1,
352 const void *p2
353 )
354 {
355 register const CNTPTR *sc1 = (const CNTPTR *)p1;
356 register const CNTPTR *sc2 = (const CNTPTR *)p2;
357
358 if (sc1->brt > sc2->brt)
359 return(-1);
360 if (sc1->brt < sc2->brt)
361 return(1);
362 return(0);
363 }
364
365
366 extern void
367 direct( /* add direct component */
368 RAY *r, /* ray that hit surface */
369 srcdirf_t *f, /* direct component coefficient function */
370 void *p /* data for f */
371 )
372 {
373 register int sn;
374 register CONTRIB *scp;
375 SRCINDEX si;
376 int nshadcheck, ncnts;
377 int nhits;
378 double prob, ourthresh, hwt;
379 RAY sr;
380 /* NOTE: srccnt and cntord global so no recursion */
381 if (nsources <= 0)
382 return; /* no sources?! */
383 /* potential contributions */
384 initsrcindex(&si);
385 for (sn = 0; srcray(&sr, r, &si); sn++) {
386 if (sn >= maxcntr) {
387 maxcntr = sn + MAXSPART;
388 srccnt = (CONTRIB *)realloc((void *)srccnt,
389 maxcntr*sizeof(CONTRIB));
390 cntord = (CNTPTR *)realloc((void *)cntord,
391 maxcntr*sizeof(CNTPTR));
392 if ((srccnt == NULL) | (cntord == NULL))
393 error(SYSTEM, "out of memory in direct");
394 }
395 cntord[sn].sndx = sn;
396 scp = srccnt + sn;
397 scp->sno = sr.rsrc;
398 /* compute coefficient */
399 (*f)(scp->coef, p, sr.rdir, si.dom);
400 cntord[sn].brt = intens(scp->coef);
401 if (cntord[sn].brt <= 0.0)
402 continue;
403 #if SHADCACHE
404 /* check shadow cache */
405 if (si.np == 1 && srcblocked(&sr)) {
406 cntord[sn].brt = 0.0;
407 continue;
408 }
409 #endif
410 VCOPY(scp->dir, sr.rdir);
411 copycolor(sr.rcoef, scp->coef);
412 /* compute potential */
413 sr.revf = srcvalue;
414 rayvalue(&sr);
415 multcolor(sr.rcol, sr.rcoef);
416 copycolor(scp->val, sr.rcol);
417 cntord[sn].brt = bright(sr.rcol);
418 }
419 /* sort contributions */
420 qsort(cntord, sn, sizeof(CNTPTR), cntcmp);
421 { /* find last */
422 register int l, m;
423
424 ncnts = l = sn;
425 sn = 0;
426 while ((m = (sn + ncnts) >> 1) != l) {
427 if (cntord[m].brt > 0.0)
428 sn = m;
429 else
430 ncnts = m;
431 l = m;
432 }
433 }
434 if (ncnts == 0)
435 return; /* no contributions! */
436 /* accumulate tail */
437 for (sn = ncnts-1; sn > 0; sn--)
438 cntord[sn-1].brt += cntord[sn].brt;
439 /* compute number to check */
440 nshadcheck = pow((double)ncnts, shadcert) + .5;
441 /* modify threshold */
442 ourthresh = shadthresh / r->rweight;
443 /* test for shadows */
444 for (nhits = 0, hwt = 0.0, sn = 0; sn < ncnts;
445 hwt += (double)source[scp->sno].nhits /
446 (double)source[scp->sno].ntests,
447 sn++) {
448 /* check threshold */
449 if ((sn+nshadcheck>=ncnts ? cntord[sn].brt :
450 cntord[sn].brt-cntord[sn+nshadcheck].brt)
451 < ourthresh*bright(r->rcol))
452 break;
453 scp = srccnt + cntord[sn].sndx;
454 /* test for hit */
455 rayorigin(&sr, SHADOW, r, NULL);
456 copycolor(sr.rcoef, scp->coef);
457 VCOPY(sr.rdir, scp->dir);
458 sr.rsrc = scp->sno;
459 /* keep statistics */
460 if (source[scp->sno].ntests++ > 0xfffffff0) {
461 source[scp->sno].ntests >>= 1;
462 source[scp->sno].nhits >>= 1;
463 }
464 if (localhit(&sr, &thescene) &&
465 ( sr.ro != source[scp->sno].so ||
466 source[scp->sno].sflags & SFOLLOW )) {
467 /* follow entire path */
468 raycont(&sr);
469 if (trace != NULL)
470 (*trace)(&sr); /* trace execution */
471 if (bright(sr.rcol) <= FTINY) {
472 #if SHADCACHE
473 if ((scp <= srccnt || scp[-1].sno != scp->sno)
474 && (scp >= srccnt+ncnts-1 ||
475 scp[1].sno != scp->sno))
476 srcblocker(&sr);
477 #endif
478 continue; /* missed! */
479 }
480 rayparticipate(&sr);
481 multcolor(sr.rcol, sr.rcoef);
482 copycolor(scp->val, sr.rcol);
483 } else if (trace != NULL &&
484 (source[scp->sno].sflags & (SDISTANT|SVIRTUAL|SFOLLOW))
485 == (SDISTANT|SFOLLOW) &&
486 sourcehit(&sr) && rayshade(&sr, sr.ro->omod)) {
487 (*trace)(&sr); /* trace execution */
488 /* skip call to rayparticipate() & scp->val update */
489 }
490 /* add contribution if hit */
491 addcolor(r->rcol, scp->val);
492 nhits++;
493 source[scp->sno].nhits++;
494 }
495 /* source hit rate */
496 if (hwt > FTINY)
497 hwt = (double)nhits / hwt;
498 else
499 hwt = 0.5;
500 #ifdef DEBUG
501 sprintf(errmsg, "%d tested, %d untested, %f conditional hit rate\n",
502 sn, ncnts-sn, hwt);
503 eputs(errmsg);
504 #endif
505 /* add in untested sources */
506 for ( ; sn < ncnts; sn++) {
507 scp = srccnt + cntord[sn].sndx;
508 prob = hwt * (double)source[scp->sno].nhits /
509 (double)source[scp->sno].ntests;
510 if (prob < 1.0)
511 scalecolor(scp->val, prob);
512 addcolor(r->rcol, scp->val);
513 }
514 }
515
516
517 extern void
518 srcscatter( /* compute source scattering into ray */
519 register RAY *r
520 )
521 {
522 int oldsampndx;
523 int nsamps;
524 RAY sr;
525 SRCINDEX si;
526 double t, d;
527 double re, ge, be;
528 COLOR cvext;
529 int i, j;
530
531 if (r->slights == NULL || r->slights[0] == 0
532 || r->gecc >= 1.-FTINY || r->rot >= FHUGE)
533 return;
534 if (ssampdist <= FTINY || (nsamps = r->rot/ssampdist + .5) < 1)
535 nsamps = 1;
536 #if MAXSSAMP
537 else if (nsamps > MAXSSAMP)
538 nsamps = MAXSSAMP;
539 #endif
540 oldsampndx = samplendx;
541 samplendx = random()&0x7fff; /* randomize */
542 for (i = r->slights[0]; i > 0; i--) { /* for each source */
543 for (j = 0; j < nsamps; j++) { /* for each sample position */
544 samplendx++;
545 t = r->rot * (j+frandom())/nsamps;
546 /* extinction */
547 re = t*colval(r->cext,RED);
548 ge = t*colval(r->cext,GRN);
549 be = t*colval(r->cext,BLU);
550 setcolor(cvext, re > 92. ? 0. : exp(-re),
551 ge > 92. ? 0. : exp(-ge),
552 be > 92. ? 0. : exp(-be));
553 if (intens(cvext) <= FTINY)
554 break; /* too far away */
555 sr.rorg[0] = r->rorg[0] + r->rdir[0]*t;
556 sr.rorg[1] = r->rorg[1] + r->rdir[1]*t;
557 sr.rorg[2] = r->rorg[2] + r->rdir[2]*t;
558 initsrcindex(&si); /* sample ray to this source */
559 si.sn = r->slights[i];
560 nopart(&si, &sr);
561 if (!srcray(&sr, NULL, &si) ||
562 sr.rsrc != r->slights[i])
563 continue; /* no path */
564 #if SHADCACHE
565 if (srcblocked(&sr)) /* check shadow cache */
566 continue;
567 #endif
568 copycolor(sr.cext, r->cext);
569 copycolor(sr.albedo, r->albedo);
570 sr.gecc = r->gecc;
571 sr.slights = r->slights;
572 rayvalue(&sr); /* eval. source ray */
573 if (bright(sr.rcol) <= FTINY) {
574 #if SHADCACHE
575 srcblocker(&sr); /* add blocker to cache */
576 #endif
577 continue;
578 }
579 if (r->gecc <= FTINY) /* compute P(theta) */
580 d = 1.;
581 else {
582 d = DOT(r->rdir, sr.rdir);
583 d = 1. + r->gecc*r->gecc - 2.*r->gecc*d;
584 d = (1. - r->gecc*r->gecc) / (d*sqrt(d));
585 }
586 /* other factors */
587 d *= si.dom * r->rot / (4.*PI*nsamps);
588 multcolor(sr.rcol, r->cext);
589 multcolor(sr.rcol, r->albedo);
590 scalecolor(sr.rcol, d);
591 multcolor(sr.rcol, cvext);
592 addcolor(r->rcol, sr.rcol); /* add it in */
593 }
594 }
595 samplendx = oldsampndx;
596 }
597
598
599 /****************************************************************
600 * The following macros were separated from the m_light() routine
601 * because they are very nasty and difficult to understand.
602 */
603
604 /* illumblock *
605 *
606 * We cannot allow an illum to pass to another illum, because that
607 * would almost certainly constitute overcounting.
608 * However, we do allow an illum to pass to another illum
609 * that is actually going to relay to a virtual light source.
610 * We also prevent an illum from passing to a glow; this provides a
611 * convenient mechanism for defining detailed light source
612 * geometry behind (or inside) an effective radiator.
613 */
614
615 static int
616 weaksrcmat(OBJECT obj) /* identify material */
617 {
618 OBJREC *m = findmaterial(objptr(obj));
619
620 if (m == NULL) return(0);
621 return((m->otype==MAT_ILLUM) | (m->otype==MAT_GLOW));
622 }
623
624 #define illumblock(m, r) (!(source[r->rsrc].sflags&SVIRTUAL) && \
625 r->rod > 0.0 && \
626 weaksrcmat(source[r->rsrc].so->omod))
627
628 /* wrongsource *
629 *
630 * This source is the wrong source (ie. overcounted) if we are
631 * aimed to a different source than the one we hit and the one
632 * we hit is not an illum that should be passed.
633 */
634
635 #define wrongsource(m, r) (r->rsrc>=0 && source[r->rsrc].so!=r->ro && \
636 (m->otype!=MAT_ILLUM || illumblock(m,r)))
637
638 /* distglow *
639 *
640 * A distant glow is an object that sometimes acts as a light source,
641 * but is too far away from the test point to be one in this case.
642 * (Glows with negative radii should NEVER participate in illumination.)
643 */
644
645 #define distglow(m, r, d) (m->otype==MAT_GLOW && \
646 m->oargs.farg[3] >= -FTINY && \
647 d > m->oargs.farg[3])
648
649 /* badcomponent *
650 *
651 * We must avoid counting light sources in the ambient calculation,
652 * since the direct component is handled separately. Therefore, any
653 * ambient ray which hits an active light source must be discarded.
654 * The same is true for stray specular samples, since the specular
655 * contribution from light sources is calculated separately.
656 */
657
658 #define badcomponent(m, r) (r->crtype&(AMBIENT|SPECULAR) && \
659 !(r->crtype&SHADOW || r->rod < 0.0 || \
660 /* not 100% correct */ distglow(m, r, r->rot)))
661
662 /* passillum *
663 *
664 * An illum passes to another material type when we didn't hit it
665 * on purpose (as part of a direct calculation), or it is relaying
666 * a virtual light source.
667 */
668
669 #define passillum(m, r) (m->otype==MAT_ILLUM && \
670 (r->rsrc<0 || source[r->rsrc].so!=r->ro || \
671 source[r->rsrc].sflags&SVIRTUAL))
672
673 /* srcignore *
674 *
675 * The -dv flag is normally on for sources to be visible.
676 */
677
678 #define srcignore(m, r) !(directvis || r->crtype&SHADOW || \
679 distglow(m, r, raydist(r,PRIMARY)))
680
681
682 extern int
683 m_light( /* ray hit a light source */
684 register OBJREC *m,
685 register RAY *r
686 )
687 {
688 /* check for over-counting */
689 if (badcomponent(m, r)) {
690 setcolor(r->rcoef, 0.0, 0.0, 0.0);
691 return(1);
692 }
693 if (wrongsource(m, r)) {
694 setcolor(r->rcoef, 0.0, 0.0, 0.0);
695 return(1);
696 }
697 /* check for passed illum */
698 if (passillum(m, r)) {
699 if (m->oargs.nsargs && strcmp(m->oargs.sarg[0], VOIDID))
700 return(rayshade(r,lastmod(objndx(m),m->oargs.sarg[0])));
701 raytrans(r);
702 return(1);
703 }
704 /* check for invisibility */
705 if (srcignore(m, r)) {
706 setcolor(r->rcoef, 0.0, 0.0, 0.0);
707 return(1);
708 }
709 /* otherwise treat as source */
710 /* check for behind */
711 if (r->rod < 0.0)
712 return(1);
713 /* check for outside spot */
714 if (m->otype==MAT_SPOT && spotout(r, makespot(m)))
715 return(1);
716 /* get distribution pattern */
717 raytexture(r, m->omod);
718 /* get source color */
719 setcolor(r->rcol, m->oargs.farg[0],
720 m->oargs.farg[1],
721 m->oargs.farg[2]);
722 /* modify value */
723 multcolor(r->rcol, r->pcol);
724 return(1);
725 }