ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf.c
Revision: 2.8
Committed: Sun Sep 26 15:43:26 2010 UTC (13 years, 7 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.7: +6 -5 lines
Log Message:
Slight change to reporting thresholds for dubious BSDFs

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bsdf.c,v 2.7 2010/09/07 23:10:50 greg Exp $";
3 #endif
4 /*
5 * Routines for handling BSDF data
6 */
7
8 #include "standard.h"
9 #include "bsdf.h"
10 #include "paths.h"
11 #include "ezxml.h"
12 #include <ctype.h>
13
14 #define MAXLATS 46 /* maximum number of latitudes */
15
16 /* BSDF angle specification */
17 typedef struct {
18 char name[64]; /* basis name */
19 int nangles; /* total number of directions */
20 struct {
21 float tmin; /* starting theta */
22 short nphis; /* number of phis (0 term) */
23 } lat[MAXLATS+1]; /* latitudes */
24 } ANGLE_BASIS;
25
26 #define MAXABASES 7 /* limit on defined bases */
27
28 static ANGLE_BASIS abase_list[MAXABASES] = {
29 {
30 "LBNL/Klems Full", 145,
31 { {-5., 1},
32 {5., 8},
33 {15., 16},
34 {25., 20},
35 {35., 24},
36 {45., 24},
37 {55., 24},
38 {65., 16},
39 {75., 12},
40 {90., 0} }
41 }, {
42 "LBNL/Klems Half", 73,
43 { {-6.5, 1},
44 {6.5, 8},
45 {19.5, 12},
46 {32.5, 16},
47 {46.5, 20},
48 {61.5, 12},
49 {76.5, 4},
50 {90., 0} }
51 }, {
52 "LBNL/Klems Quarter", 41,
53 { {-9., 1},
54 {9., 8},
55 {27., 12},
56 {46., 12},
57 {66., 8},
58 {90., 0} }
59 }
60 };
61
62 static int nabases = 3; /* current number of defined bases */
63
64 #define FEQ(a,b) ((a)-(b) <= 1e-7 && (b)-(a) <= 1e-7)
65
66 // returns the name of the given tag
67 #ifdef ezxml_name
68 #undef ezxml_name
69 static char *
70 ezxml_name(ezxml_t xml)
71 {
72 if (xml == NULL)
73 return(NULL);
74 return(xml->name);
75 }
76 #endif
77
78 // returns the given tag's character content or empty string if none
79 #ifdef ezxml_txt
80 #undef ezxml_txt
81 static char *
82 ezxml_txt(ezxml_t xml)
83 {
84 if (xml == NULL)
85 return("");
86 return(xml->txt);
87 }
88 #endif
89
90
91 static int
92 ab_getvec( /* get vector for this angle basis index */
93 FVECT v,
94 int ndx,
95 void *p
96 )
97 {
98 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
99 int li;
100 double pol, azi, d;
101
102 if ((ndx < 0) | (ndx >= ab->nangles))
103 return(0);
104 for (li = 0; ndx >= ab->lat[li].nphis; li++)
105 ndx -= ab->lat[li].nphis;
106 pol = PI/180.*0.5*(ab->lat[li].tmin + ab->lat[li+1].tmin);
107 azi = 2.*PI*ndx/ab->lat[li].nphis;
108 v[2] = d = cos(pol);
109 d = sqrt(1. - d*d); /* sin(pol) */
110 v[0] = cos(azi)*d;
111 v[1] = sin(azi)*d;
112 return(1);
113 }
114
115
116 static int
117 ab_getndx( /* get index corresponding to the given vector */
118 FVECT v,
119 void *p
120 )
121 {
122 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
123 int li, ndx;
124 double pol, azi, d;
125
126 if ((v[2] < -1.0) | (v[2] > 1.0))
127 return(-1);
128 pol = 180.0/PI*acos(v[2]);
129 azi = 180.0/PI*atan2(v[1], v[0]);
130 if (azi < 0.0) azi += 360.0;
131 for (li = 1; ab->lat[li].tmin <= pol; li++)
132 if (!ab->lat[li].nphis)
133 return(-1);
134 --li;
135 ndx = (int)((1./360.)*azi*ab->lat[li].nphis + 0.5);
136 if (ndx >= ab->lat[li].nphis) ndx = 0;
137 while (li--)
138 ndx += ab->lat[li].nphis;
139 return(ndx);
140 }
141
142
143 static double
144 ab_getohm( /* get solid angle for this angle basis index */
145 int ndx,
146 void *p
147 )
148 {
149 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
150 int li;
151 double theta, theta1;
152
153 if ((ndx < 0) | (ndx >= ab->nangles))
154 return(0);
155 for (li = 0; ndx >= ab->lat[li].nphis; li++)
156 ndx -= ab->lat[li].nphis;
157 theta1 = PI/180. * ab->lat[li+1].tmin;
158 if (ab->lat[li].nphis == 1) { /* special case */
159 if (ab->lat[li].tmin > FTINY)
160 error(USER, "unsupported BSDF coordinate system");
161 return(2.*PI*(1. - cos(theta1)));
162 }
163 theta = PI/180. * ab->lat[li].tmin;
164 return(2.*PI*(cos(theta) - cos(theta1))/(double)ab->lat[li].nphis);
165 }
166
167
168 static int
169 ab_getvecR( /* get reverse vector for this angle basis index */
170 FVECT v,
171 int ndx,
172 void *p
173 )
174 {
175 if (!ab_getvec(v, ndx, p))
176 return(0);
177
178 v[0] = -v[0];
179 v[1] = -v[1];
180 v[2] = -v[2];
181
182 return(1);
183 }
184
185
186 static int
187 ab_getndxR( /* get index corresponding to the reverse vector */
188 FVECT v,
189 void *p
190 )
191 {
192 FVECT v2;
193
194 v2[0] = -v[0];
195 v2[1] = -v[1];
196 v2[2] = -v[2];
197
198 return ab_getndx(v2, p);
199 }
200
201
202 static void
203 load_angle_basis( /* load custom BSDF angle basis */
204 ezxml_t wab
205 )
206 {
207 char *abname = ezxml_txt(ezxml_child(wab, "AngleBasisName"));
208 ezxml_t wbb;
209 int i;
210
211 if (!abname || !*abname)
212 return;
213 for (i = nabases; i--; )
214 if (!strcmp(abname, abase_list[i].name))
215 return; /* assume it's the same */
216 if (nabases >= MAXABASES)
217 error(INTERNAL, "too many angle bases");
218 strcpy(abase_list[nabases].name, abname);
219 abase_list[nabases].nangles = 0;
220 for (i = 0, wbb = ezxml_child(wab, "AngleBasisBlock");
221 wbb != NULL; i++, wbb = wbb->next) {
222 if (i >= MAXLATS)
223 error(INTERNAL, "too many latitudes in custom basis");
224 abase_list[nabases].lat[i+1].tmin = atof(ezxml_txt(
225 ezxml_child(ezxml_child(wbb,
226 "ThetaBounds"), "UpperTheta")));
227 if (!i)
228 abase_list[nabases].lat[i].tmin =
229 -abase_list[nabases].lat[i+1].tmin;
230 else if (!FEQ(atof(ezxml_txt(ezxml_child(ezxml_child(wbb,
231 "ThetaBounds"), "LowerTheta"))),
232 abase_list[nabases].lat[i].tmin))
233 error(WARNING, "theta values disagree in custom basis");
234 abase_list[nabases].nangles +=
235 abase_list[nabases].lat[i].nphis =
236 atoi(ezxml_txt(ezxml_child(wbb, "nPhis")));
237 }
238 abase_list[nabases++].lat[i].nphis = 0;
239 }
240
241
242 static double
243 to_meters( /* return factor to convert given unit to meters */
244 const char *unit
245 )
246 {
247 if (unit == NULL) return(1.); /* safe assumption? */
248 if (!strcasecmp(unit, "Meter")) return(1.);
249 if (!strcasecmp(unit, "Foot")) return(.3048);
250 if (!strcasecmp(unit, "Inch")) return(.0254);
251 if (!strcasecmp(unit, "Centimeter")) return(.01);
252 if (!strcasecmp(unit, "Millimeter")) return(.001);
253 sprintf(errmsg, "unknown dimensional unit '%s'", unit);
254 error(USER, errmsg);
255 }
256
257
258 static void
259 load_geometry( /* load geometric dimensions and description (if any) */
260 struct BSDF_data *dp,
261 ezxml_t wdb
262 )
263 {
264 ezxml_t geom;
265 double cfact;
266 const char *fmt, *mgfstr;
267
268 dp->dim[0] = dp->dim[1] = dp->dim[2] = 0;
269 dp->mgf = NULL;
270 if ((geom = ezxml_child(wdb, "Width")) != NULL)
271 dp->dim[0] = atof(ezxml_txt(geom)) *
272 to_meters(ezxml_attr(geom, "unit"));
273 if ((geom = ezxml_child(wdb, "Height")) != NULL)
274 dp->dim[1] = atof(ezxml_txt(geom)) *
275 to_meters(ezxml_attr(geom, "unit"));
276 if ((geom = ezxml_child(wdb, "Thickness")) != NULL)
277 dp->dim[2] = atof(ezxml_txt(geom)) *
278 to_meters(ezxml_attr(geom, "unit"));
279 if ((geom = ezxml_child(wdb, "Geometry")) == NULL ||
280 (mgfstr = ezxml_txt(geom)) == NULL)
281 return;
282 if ((fmt = ezxml_attr(geom, "format")) != NULL &&
283 strcasecmp(fmt, "MGF")) {
284 sprintf(errmsg, "unrecognized geometry format '%s'", fmt);
285 error(WARNING, errmsg);
286 return;
287 }
288 cfact = to_meters(ezxml_attr(geom, "unit"));
289 dp->mgf = (char *)malloc(strlen(mgfstr)+32);
290 if (dp->mgf == NULL)
291 error(SYSTEM, "out of memory in load_geometry");
292 if (cfact < 0.99 || cfact > 1.01)
293 sprintf(dp->mgf, "xf -s %.5f\n%s\nxf\n", cfact, mgfstr);
294 else
295 strcpy(dp->mgf, mgfstr);
296 }
297
298
299 static void
300 load_bsdf_data( /* load BSDF distribution for this wavelength */
301 struct BSDF_data *dp,
302 ezxml_t wdb
303 )
304 {
305 char *cbasis = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
306 char *rbasis = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
307 char *sdata;
308 int i;
309
310 if ((!cbasis || !*cbasis) | (!rbasis || !*rbasis)) {
311 error(WARNING, "missing column/row basis for BSDF");
312 return;
313 }
314 for (i = nabases; i--; )
315 if (!strcmp(cbasis, abase_list[i].name)) {
316 dp->ninc = abase_list[i].nangles;
317 dp->ib_priv = (void *)&abase_list[i];
318 dp->ib_vec = ab_getvecR;
319 dp->ib_ndx = ab_getndxR;
320 dp->ib_ohm = ab_getohm;
321 break;
322 }
323 if (i < 0) {
324 sprintf(errmsg, "undefined ColumnAngleBasis '%s'", cbasis);
325 error(WARNING, errmsg);
326 return;
327 }
328 for (i = nabases; i--; )
329 if (!strcmp(rbasis, abase_list[i].name)) {
330 dp->nout = abase_list[i].nangles;
331 dp->ob_priv = (void *)&abase_list[i];
332 dp->ob_vec = ab_getvec;
333 dp->ob_ndx = ab_getndx;
334 dp->ob_ohm = ab_getohm;
335 break;
336 }
337 if (i < 0) {
338 sprintf(errmsg, "undefined RowAngleBasis '%s'", cbasis);
339 error(WARNING, errmsg);
340 return;
341 }
342 /* read BSDF data */
343 sdata = ezxml_txt(ezxml_child(wdb,"ScatteringData"));
344 if (!sdata || !*sdata) {
345 error(WARNING, "missing BSDF ScatteringData");
346 return;
347 }
348 dp->bsdf = (float *)malloc(sizeof(float)*dp->ninc*dp->nout);
349 if (dp->bsdf == NULL)
350 error(SYSTEM, "out of memory in load_bsdf_data");
351 for (i = 0; i < dp->ninc*dp->nout; i++) {
352 char *sdnext = fskip(sdata);
353 if (sdnext == NULL) {
354 error(WARNING, "bad/missing BSDF ScatteringData");
355 free(dp->bsdf); dp->bsdf = NULL;
356 return;
357 }
358 while (*sdnext && isspace(*sdnext))
359 sdnext++;
360 if (*sdnext == ',') sdnext++;
361 dp->bsdf[i] = atof(sdata);
362 sdata = sdnext;
363 }
364 while (isspace(*sdata))
365 sdata++;
366 if (*sdata) {
367 sprintf(errmsg, "%d extra characters after BSDF ScatteringData",
368 (int)strlen(sdata));
369 error(WARNING, errmsg);
370 }
371 }
372
373
374 static int
375 check_bsdf_data( /* check that BSDF data is sane */
376 struct BSDF_data *dp
377 )
378 {
379 double *omega_iarr, *omega_oarr;
380 double dom, contrib, hemi_total, full_total;
381 int nneg;
382 FVECT v;
383 int i, o;
384
385 if (dp == NULL || dp->bsdf == NULL)
386 return(0);
387 omega_iarr = (double *)calloc(dp->ninc, sizeof(double));
388 omega_oarr = (double *)calloc(dp->nout, sizeof(double));
389 if ((omega_iarr == NULL) | (omega_oarr == NULL))
390 error(SYSTEM, "out of memory in check_bsdf_data");
391 /* incoming projected solid angles */
392 hemi_total = .0;
393 for (i = dp->ninc; i--; ) {
394 dom = getBSDF_incohm(dp,i);
395 if (dom <= .0) {
396 error(WARNING, "zero/negative incoming solid angle");
397 continue;
398 }
399 if (!getBSDF_incvec(v,dp,i) || v[2] > FTINY) {
400 error(WARNING, "illegal incoming BSDF direction");
401 free(omega_iarr); free(omega_oarr);
402 return(0);
403 }
404 hemi_total += omega_iarr[i] = dom * -v[2];
405 }
406 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
407 sprintf(errmsg, "incoming BSDF hemisphere off by %.1f%%",
408 100.*(hemi_total/PI - 1.));
409 error(WARNING, errmsg);
410 }
411 dom = PI / hemi_total; /* fix normalization */
412 for (i = dp->ninc; i--; )
413 omega_iarr[i] *= dom;
414 /* outgoing projected solid angles */
415 hemi_total = .0;
416 for (o = dp->nout; o--; ) {
417 dom = getBSDF_outohm(dp,o);
418 if (dom <= .0) {
419 error(WARNING, "zero/negative outgoing solid angle");
420 continue;
421 }
422 if (!getBSDF_outvec(v,dp,o) || v[2] < -FTINY) {
423 error(WARNING, "illegal outgoing BSDF direction");
424 free(omega_iarr); free(omega_oarr);
425 return(0);
426 }
427 hemi_total += omega_oarr[o] = dom * v[2];
428 }
429 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
430 sprintf(errmsg, "outgoing BSDF hemisphere off by %.1f%%",
431 100.*(hemi_total/PI - 1.));
432 error(WARNING, errmsg);
433 }
434 dom = PI / hemi_total; /* fix normalization */
435 for (o = dp->nout; o--; )
436 omega_oarr[o] *= dom;
437 nneg = 0; /* check outgoing totals */
438 for (i = 0; i < dp->ninc; i++) {
439 hemi_total = .0;
440 for (o = dp->nout; o--; ) {
441 double f = BSDF_value(dp,i,o);
442 if (f >= .0)
443 hemi_total += f*omega_oarr[o];
444 else {
445 nneg += (f < -FTINY);
446 BSDF_value(dp,i,o) = .0f;
447 }
448 }
449 if (hemi_total > 1.01) {
450 sprintf(errmsg,
451 "incoming BSDF direction %d passes %.1f%% of light",
452 i, 100.*hemi_total);
453 error(WARNING, errmsg);
454 }
455 }
456 if (nneg) {
457 sprintf(errmsg, "%d negative BSDF values (ignored)", nneg);
458 error(WARNING, errmsg);
459 }
460 full_total = .0; /* reverse roles and check again */
461 for (o = 0; o < dp->nout; o++) {
462 hemi_total = .0;
463 for (i = dp->ninc; i--; )
464 hemi_total += BSDF_value(dp,i,o) * omega_iarr[i];
465
466 if (hemi_total > 1.01) {
467 sprintf(errmsg,
468 "outgoing BSDF direction %d collects %.1f%% of light",
469 o, 100.*hemi_total);
470 error(WARNING, errmsg);
471 }
472 full_total += hemi_total*omega_oarr[o];
473 }
474 full_total /= PI;
475 if (full_total > 1.00001) {
476 sprintf(errmsg, "BSDF transfers %.4f%% of light",
477 100.*full_total);
478 error(WARNING, errmsg);
479 }
480 free(omega_iarr); free(omega_oarr);
481 return(1);
482 }
483
484
485 struct BSDF_data *
486 load_BSDF( /* load BSDF data from file */
487 char *fname
488 )
489 {
490 char *path;
491 ezxml_t fl, wtl, wld, wdb;
492 struct BSDF_data *dp;
493
494 path = getpath(fname, getrlibpath(), R_OK);
495 if (path == NULL) {
496 sprintf(errmsg, "cannot find BSDF file \"%s\"", fname);
497 error(WARNING, errmsg);
498 return(NULL);
499 }
500 fl = ezxml_parse_file(path);
501 if (fl == NULL) {
502 sprintf(errmsg, "cannot open BSDF \"%s\"", path);
503 error(WARNING, errmsg);
504 return(NULL);
505 }
506 if (ezxml_error(fl)[0]) {
507 sprintf(errmsg, "BSDF \"%s\" %s", path, ezxml_error(fl));
508 error(WARNING, errmsg);
509 ezxml_free(fl);
510 return(NULL);
511 }
512 if (strcmp(ezxml_name(fl), "WindowElement")) {
513 sprintf(errmsg,
514 "BSDF \"%s\": top level node not 'WindowElement'",
515 path);
516 error(WARNING, errmsg);
517 ezxml_free(fl);
518 return(NULL);
519 }
520 wtl = ezxml_child(ezxml_child(fl, "Optical"), "Layer");
521 load_angle_basis(ezxml_child(ezxml_child(wtl,
522 "DataDefinition"), "AngleBasis"));
523 dp = (struct BSDF_data *)calloc(1, sizeof(struct BSDF_data));
524 load_geometry(dp, ezxml_child(wtl, "Material"));
525 for (wld = ezxml_child(wtl, "WavelengthData");
526 wld != NULL; wld = wld->next) {
527 if (strcmp(ezxml_txt(ezxml_child(wld,"Wavelength")), "Visible"))
528 continue;
529 wdb = ezxml_child(wld, "WavelengthDataBlock");
530 if (wdb == NULL) continue;
531 if (strcmp(ezxml_txt(ezxml_child(wdb,"WavelengthDataDirection")),
532 "Transmission Front"))
533 continue;
534 load_bsdf_data(dp, wdb); /* load front BTDF */
535 break; /* ignore the rest */
536 }
537 ezxml_free(fl); /* done with XML file */
538 if (!check_bsdf_data(dp)) {
539 sprintf(errmsg, "bad/missing BTDF data in \"%s\"", path);
540 error(WARNING, errmsg);
541 free_BSDF(dp);
542 dp = NULL;
543 }
544 return(dp);
545 }
546
547
548 void
549 free_BSDF( /* free BSDF data structure */
550 struct BSDF_data *b
551 )
552 {
553 if (b == NULL)
554 return;
555 if (b->mgf != NULL)
556 free(b->mgf);
557 if (b->bsdf != NULL)
558 free(b->bsdf);
559 free(b);
560 }
561
562
563 int
564 r_BSDF_incvec( /* compute random input vector at given location */
565 FVECT v,
566 struct BSDF_data *b,
567 int i,
568 double rv,
569 MAT4 xm
570 )
571 {
572 FVECT pert;
573 double rad;
574 int j;
575
576 if (!getBSDF_incvec(v, b, i))
577 return(0);
578 rad = sqrt(getBSDF_incohm(b, i) / PI);
579 multisamp(pert, 3, rv);
580 for (j = 0; j < 3; j++)
581 v[j] += rad*(2.*pert[j] - 1.);
582 if (xm != NULL)
583 multv3(v, v, xm);
584 return(normalize(v) != 0.0);
585 }
586
587
588 int
589 r_BSDF_outvec( /* compute random output vector at given location */
590 FVECT v,
591 struct BSDF_data *b,
592 int o,
593 double rv,
594 MAT4 xm
595 )
596 {
597 FVECT pert;
598 double rad;
599 int j;
600
601 if (!getBSDF_outvec(v, b, o))
602 return(0);
603 rad = sqrt(getBSDF_outohm(b, o) / PI);
604 multisamp(pert, 3, rv);
605 for (j = 0; j < 3; j++)
606 v[j] += rad*(2.*pert[j] - 1.);
607 if (xm != NULL)
608 multv3(v, v, xm);
609 return(normalize(v) != 0.0);
610 }
611
612
613 static int
614 addrot( /* compute rotation (x,y,z) => (xp,yp,zp) */
615 char *xfarg[],
616 FVECT xp,
617 FVECT yp,
618 FVECT zp
619 )
620 {
621 static char bufs[3][16];
622 int bn = 0;
623 char **xfp = xfarg;
624 double theta;
625
626 if (yp[2]*yp[2] + zp[2]*zp[2] < 2.*FTINY*FTINY) {
627 /* Special case for X' along Z-axis */
628 theta = -atan2(yp[0], yp[1]);
629 *xfp++ = "-ry";
630 *xfp++ = xp[2] < 0.0 ? "90" : "-90";
631 *xfp++ = "-rz";
632 sprintf(bufs[bn], "%f", theta*(180./PI));
633 *xfp++ = bufs[bn++];
634 return(xfp - xfarg);
635 }
636 theta = atan2(yp[2], zp[2]);
637 if (!FEQ(theta,0.0)) {
638 *xfp++ = "-rx";
639 sprintf(bufs[bn], "%f", theta*(180./PI));
640 *xfp++ = bufs[bn++];
641 }
642 theta = asin(-xp[2]);
643 if (!FEQ(theta,0.0)) {
644 *xfp++ = "-ry";
645 sprintf(bufs[bn], " %f", theta*(180./PI));
646 *xfp++ = bufs[bn++];
647 }
648 theta = atan2(xp[1], xp[0]);
649 if (!FEQ(theta,0.0)) {
650 *xfp++ = "-rz";
651 sprintf(bufs[bn], "%f", theta*(180./PI));
652 *xfp++ = bufs[bn++];
653 }
654 *xfp = NULL;
655 return(xfp - xfarg);
656 }
657
658
659 int
660 getBSDF_xfm( /* compute BSDF orient. -> world orient. transform */
661 MAT4 xm,
662 FVECT nrm,
663 UpDir ud,
664 char *xfbuf
665 )
666 {
667 char *xfargs[7];
668 XF myxf;
669 FVECT updir, xdest, ydest;
670 int i;
671
672 updir[0] = updir[1] = updir[2] = 0.;
673 switch (ud) {
674 case UDzneg:
675 updir[2] = -1.;
676 break;
677 case UDyneg:
678 updir[1] = -1.;
679 break;
680 case UDxneg:
681 updir[0] = -1.;
682 break;
683 case UDxpos:
684 updir[0] = 1.;
685 break;
686 case UDypos:
687 updir[1] = 1.;
688 break;
689 case UDzpos:
690 updir[2] = 1.;
691 break;
692 case UDunknown:
693 return(0);
694 }
695 fcross(xdest, updir, nrm);
696 if (normalize(xdest) == 0.0)
697 return(0);
698 fcross(ydest, nrm, xdest);
699 xf(&myxf, addrot(xfargs, xdest, ydest, nrm), xfargs);
700 copymat4(xm, myxf.xfm);
701 if (xfbuf == NULL)
702 return(1);
703 /* return xf arguments as well */
704 for (i = 0; xfargs[i] != NULL; i++) {
705 *xfbuf++ = ' ';
706 strcpy(xfbuf, xfargs[i]);
707 while (*xfbuf) ++xfbuf;
708 }
709 return(1);
710 }