ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf.c
Revision: 2.7
Committed: Tue Sep 7 23:10:50 2010 UTC (13 years, 8 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.6: +10 -3 lines
Log Message:
Added test for full light transfer in BSDF

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bsdf.c,v 2.6 2010/09/03 23:53: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 sprintf(errmsg, "unknown dimensional unit '%s'", unit);
253 error(USER, errmsg);
254 }
255
256
257 static void
258 load_geometry( /* load geometric dimensions and description (if any) */
259 struct BSDF_data *dp,
260 ezxml_t wdb
261 )
262 {
263 ezxml_t geom;
264 double cfact;
265 const char *fmt, *mgfstr;
266
267 dp->dim[0] = dp->dim[1] = dp->dim[2] = 0;
268 dp->mgf = NULL;
269 if ((geom = ezxml_child(wdb, "Width")) != NULL)
270 dp->dim[0] = atof(ezxml_txt(geom)) *
271 to_meters(ezxml_attr(geom, "unit"));
272 if ((geom = ezxml_child(wdb, "Height")) != NULL)
273 dp->dim[1] = atof(ezxml_txt(geom)) *
274 to_meters(ezxml_attr(geom, "unit"));
275 if ((geom = ezxml_child(wdb, "Thickness")) != NULL)
276 dp->dim[2] = atof(ezxml_txt(geom)) *
277 to_meters(ezxml_attr(geom, "unit"));
278 if ((geom = ezxml_child(wdb, "Geometry")) == NULL ||
279 (mgfstr = ezxml_txt(geom)) == NULL)
280 return;
281 if ((fmt = ezxml_attr(geom, "format")) != NULL &&
282 strcasecmp(fmt, "MGF")) {
283 sprintf(errmsg, "unrecognized geometry format '%s'", fmt);
284 error(WARNING, errmsg);
285 return;
286 }
287 cfact = to_meters(ezxml_attr(geom, "unit"));
288 dp->mgf = (char *)malloc(strlen(mgfstr)+32);
289 if (dp->mgf == NULL)
290 error(SYSTEM, "out of memory in load_geometry");
291 if (cfact < 0.99 || cfact > 1.01)
292 sprintf(dp->mgf, "xf -s %.5f\n%s\nxf\n", cfact, mgfstr);
293 else
294 strcpy(dp->mgf, mgfstr);
295 }
296
297
298 static void
299 load_bsdf_data( /* load BSDF distribution for this wavelength */
300 struct BSDF_data *dp,
301 ezxml_t wdb
302 )
303 {
304 char *cbasis = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
305 char *rbasis = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
306 char *sdata;
307 int i;
308
309 if ((!cbasis || !*cbasis) | (!rbasis || !*rbasis)) {
310 error(WARNING, "missing column/row basis for BSDF");
311 return;
312 }
313 for (i = nabases; i--; )
314 if (!strcmp(cbasis, abase_list[i].name)) {
315 dp->ninc = abase_list[i].nangles;
316 dp->ib_priv = (void *)&abase_list[i];
317 dp->ib_vec = ab_getvecR;
318 dp->ib_ndx = ab_getndxR;
319 dp->ib_ohm = ab_getohm;
320 break;
321 }
322 if (i < 0) {
323 sprintf(errmsg, "undefined ColumnAngleBasis '%s'", cbasis);
324 error(WARNING, errmsg);
325 return;
326 }
327 for (i = nabases; i--; )
328 if (!strcmp(rbasis, abase_list[i].name)) {
329 dp->nout = abase_list[i].nangles;
330 dp->ob_priv = (void *)&abase_list[i];
331 dp->ob_vec = ab_getvec;
332 dp->ob_ndx = ab_getndx;
333 dp->ob_ohm = ab_getohm;
334 break;
335 }
336 if (i < 0) {
337 sprintf(errmsg, "undefined RowAngleBasis '%s'", cbasis);
338 error(WARNING, errmsg);
339 return;
340 }
341 /* read BSDF data */
342 sdata = ezxml_txt(ezxml_child(wdb,"ScatteringData"));
343 if (!sdata || !*sdata) {
344 error(WARNING, "missing BSDF ScatteringData");
345 return;
346 }
347 dp->bsdf = (float *)malloc(sizeof(float)*dp->ninc*dp->nout);
348 if (dp->bsdf == NULL)
349 error(SYSTEM, "out of memory in load_bsdf_data");
350 for (i = 0; i < dp->ninc*dp->nout; i++) {
351 char *sdnext = fskip(sdata);
352 if (sdnext == NULL) {
353 error(WARNING, "bad/missing BSDF ScatteringData");
354 free(dp->bsdf); dp->bsdf = NULL;
355 return;
356 }
357 while (*sdnext && isspace(*sdnext))
358 sdnext++;
359 if (*sdnext == ',') sdnext++;
360 dp->bsdf[i] = atof(sdata);
361 sdata = sdnext;
362 }
363 while (isspace(*sdata))
364 sdata++;
365 if (*sdata) {
366 sprintf(errmsg, "%d extra characters after BSDF ScatteringData",
367 (int)strlen(sdata));
368 error(WARNING, errmsg);
369 }
370 }
371
372
373 static int
374 check_bsdf_data( /* check that BSDF data is sane */
375 struct BSDF_data *dp
376 )
377 {
378 double *omega_iarr, *omega_oarr;
379 double dom, contrib, hemi_total, full_total;
380 int nneg;
381 FVECT v;
382 int i, o;
383
384 if (dp == NULL || dp->bsdf == NULL)
385 return(0);
386 omega_iarr = (double *)calloc(dp->ninc, sizeof(double));
387 omega_oarr = (double *)calloc(dp->nout, sizeof(double));
388 if ((omega_iarr == NULL) | (omega_oarr == NULL))
389 error(SYSTEM, "out of memory in check_bsdf_data");
390 /* incoming projected solid angles */
391 hemi_total = .0;
392 for (i = dp->ninc; i--; ) {
393 dom = getBSDF_incohm(dp,i);
394 if (dom <= .0) {
395 error(WARNING, "zero/negative incoming solid angle");
396 continue;
397 }
398 if (!getBSDF_incvec(v,dp,i) || v[2] > FTINY) {
399 error(WARNING, "illegal incoming BSDF direction");
400 free(omega_iarr); free(omega_oarr);
401 return(0);
402 }
403 hemi_total += omega_iarr[i] = dom * -v[2];
404 }
405 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
406 sprintf(errmsg, "incoming BSDF hemisphere off by %.1f%%",
407 100.*(hemi_total/PI - 1.));
408 error(WARNING, errmsg);
409 }
410 dom = PI / hemi_total; /* fix normalization */
411 for (i = dp->ninc; i--; )
412 omega_iarr[i] *= dom;
413 /* outgoing projected solid angles */
414 hemi_total = .0;
415 for (o = dp->nout; o--; ) {
416 dom = getBSDF_outohm(dp,o);
417 if (dom <= .0) {
418 error(WARNING, "zero/negative outgoing solid angle");
419 continue;
420 }
421 if (!getBSDF_outvec(v,dp,o) || v[2] < -FTINY) {
422 error(WARNING, "illegal outgoing BSDF direction");
423 free(omega_iarr); free(omega_oarr);
424 return(0);
425 }
426 hemi_total += omega_oarr[o] = dom * v[2];
427 }
428 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
429 sprintf(errmsg, "outgoing BSDF hemisphere off by %.1f%%",
430 100.*(hemi_total/PI - 1.));
431 error(WARNING, errmsg);
432 }
433 dom = PI / hemi_total; /* fix normalization */
434 for (o = dp->nout; o--; )
435 omega_oarr[o] *= dom;
436 nneg = 0; /* check outgoing totals */
437 for (i = 0; i < dp->ninc; i++) {
438 hemi_total = .0;
439 for (o = dp->nout; o--; ) {
440 double f = BSDF_value(dp,i,o);
441 if (f >= .0)
442 hemi_total += f*omega_oarr[o];
443 else {
444 nneg += (f < -FTINY);
445 BSDF_value(dp,i,o) = .0f;
446 }
447 }
448 if (hemi_total > 1.02) {
449 sprintf(errmsg,
450 "incoming BSDF direction %d passes %.1f%% of light",
451 i, 100.*hemi_total);
452 error(WARNING, errmsg);
453 }
454 }
455 if (nneg) {
456 sprintf(errmsg, "%d negative BSDF values (ignored)", nneg);
457 error(WARNING, errmsg);
458 }
459 full_total = .0; /* reverse roles and check again */
460 for (o = 0; o < dp->nout; o++) {
461 hemi_total = .0;
462 for (i = dp->ninc; i--; )
463 hemi_total += BSDF_value(dp,i,o) * omega_iarr[i];
464
465 if (hemi_total > 1.02) {
466 sprintf(errmsg,
467 "outgoing BSDF direction %d collects %.1f%% of light",
468 o, 100.*hemi_total);
469 error(WARNING, errmsg);
470 }
471 full_total += hemi_total*omega_oarr[o];
472 }
473 full_total /= PI;
474 if (full_total > 1.02) {
475 sprintf(errmsg, "BSDF transfers %.1f%% of light",
476 100.*full_total);
477 error(WARNING, errmsg);
478 }
479 free(omega_iarr); free(omega_oarr);
480 return(1);
481 }
482
483
484 struct BSDF_data *
485 load_BSDF( /* load BSDF data from file */
486 char *fname
487 )
488 {
489 char *path;
490 ezxml_t fl, wtl, wld, wdb;
491 struct BSDF_data *dp;
492
493 path = getpath(fname, getrlibpath(), R_OK);
494 if (path == NULL) {
495 sprintf(errmsg, "cannot find BSDF file \"%s\"", fname);
496 error(WARNING, errmsg);
497 return(NULL);
498 }
499 fl = ezxml_parse_file(path);
500 if (fl == NULL) {
501 sprintf(errmsg, "cannot open BSDF \"%s\"", path);
502 error(WARNING, errmsg);
503 return(NULL);
504 }
505 if (ezxml_error(fl)[0]) {
506 sprintf(errmsg, "BSDF \"%s\" %s", path, ezxml_error(fl));
507 error(WARNING, errmsg);
508 ezxml_free(fl);
509 return(NULL);
510 }
511 if (strcmp(ezxml_name(fl), "WindowElement")) {
512 sprintf(errmsg,
513 "BSDF \"%s\": top level node not 'WindowElement'",
514 path);
515 error(WARNING, errmsg);
516 ezxml_free(fl);
517 return(NULL);
518 }
519 wtl = ezxml_child(ezxml_child(fl, "Optical"), "Layer");
520 load_angle_basis(ezxml_child(ezxml_child(wtl,
521 "DataDefinition"), "AngleBasis"));
522 dp = (struct BSDF_data *)calloc(1, sizeof(struct BSDF_data));
523 load_geometry(dp, ezxml_child(wtl, "Material"));
524 for (wld = ezxml_child(wtl, "WavelengthData");
525 wld != NULL; wld = wld->next) {
526 if (strcmp(ezxml_txt(ezxml_child(wld,"Wavelength")), "Visible"))
527 continue;
528 wdb = ezxml_child(wld, "WavelengthDataBlock");
529 if (wdb == NULL) continue;
530 if (strcmp(ezxml_txt(ezxml_child(wdb,"WavelengthDataDirection")),
531 "Transmission Front"))
532 continue;
533 load_bsdf_data(dp, wdb); /* load front BTDF */
534 break; /* ignore the rest */
535 }
536 ezxml_free(fl); /* done with XML file */
537 if (!check_bsdf_data(dp)) {
538 sprintf(errmsg, "bad/missing BTDF data in \"%s\"", path);
539 error(WARNING, errmsg);
540 free_BSDF(dp);
541 dp = NULL;
542 }
543 return(dp);
544 }
545
546
547 void
548 free_BSDF( /* free BSDF data structure */
549 struct BSDF_data *b
550 )
551 {
552 if (b == NULL)
553 return;
554 if (b->mgf != NULL)
555 free(b->mgf);
556 if (b->bsdf != NULL)
557 free(b->bsdf);
558 free(b);
559 }
560
561
562 int
563 r_BSDF_incvec( /* compute random input vector at given location */
564 FVECT v,
565 struct BSDF_data *b,
566 int i,
567 double rv,
568 MAT4 xm
569 )
570 {
571 FVECT pert;
572 double rad;
573 int j;
574
575 if (!getBSDF_incvec(v, b, i))
576 return(0);
577 rad = sqrt(getBSDF_incohm(b, i) / PI);
578 multisamp(pert, 3, rv);
579 for (j = 0; j < 3; j++)
580 v[j] += rad*(2.*pert[j] - 1.);
581 if (xm != NULL)
582 multv3(v, v, xm);
583 return(normalize(v) != 0.0);
584 }
585
586
587 int
588 r_BSDF_outvec( /* compute random output vector at given location */
589 FVECT v,
590 struct BSDF_data *b,
591 int o,
592 double rv,
593 MAT4 xm
594 )
595 {
596 FVECT pert;
597 double rad;
598 int j;
599
600 if (!getBSDF_outvec(v, b, o))
601 return(0);
602 rad = sqrt(getBSDF_outohm(b, o) / PI);
603 multisamp(pert, 3, rv);
604 for (j = 0; j < 3; j++)
605 v[j] += rad*(2.*pert[j] - 1.);
606 if (xm != NULL)
607 multv3(v, v, xm);
608 return(normalize(v) != 0.0);
609 }
610
611
612 static int
613 addrot( /* compute rotation (x,y,z) => (xp,yp,zp) */
614 char *xfarg[],
615 FVECT xp,
616 FVECT yp,
617 FVECT zp
618 )
619 {
620 static char bufs[3][16];
621 int bn = 0;
622 char **xfp = xfarg;
623 double theta;
624
625 if (yp[2]*yp[2] + zp[2]*zp[2] < 2.*FTINY*FTINY) {
626 /* Special case for X' along Z-axis */
627 theta = -atan2(yp[0], yp[1]);
628 *xfp++ = "-ry";
629 *xfp++ = xp[2] < 0.0 ? "90" : "-90";
630 *xfp++ = "-rz";
631 sprintf(bufs[bn], "%f", theta*(180./PI));
632 *xfp++ = bufs[bn++];
633 return(xfp - xfarg);
634 }
635 theta = atan2(yp[2], zp[2]);
636 if (!FEQ(theta,0.0)) {
637 *xfp++ = "-rx";
638 sprintf(bufs[bn], "%f", theta*(180./PI));
639 *xfp++ = bufs[bn++];
640 }
641 theta = asin(-xp[2]);
642 if (!FEQ(theta,0.0)) {
643 *xfp++ = "-ry";
644 sprintf(bufs[bn], " %f", theta*(180./PI));
645 *xfp++ = bufs[bn++];
646 }
647 theta = atan2(xp[1], xp[0]);
648 if (!FEQ(theta,0.0)) {
649 *xfp++ = "-rz";
650 sprintf(bufs[bn], "%f", theta*(180./PI));
651 *xfp++ = bufs[bn++];
652 }
653 *xfp = NULL;
654 return(xfp - xfarg);
655 }
656
657
658 int
659 getBSDF_xfm( /* compute BSDF orient. -> world orient. transform */
660 MAT4 xm,
661 FVECT nrm,
662 UpDir ud,
663 char *xfbuf
664 )
665 {
666 char *xfargs[7];
667 XF myxf;
668 FVECT updir, xdest, ydest;
669 int i;
670
671 updir[0] = updir[1] = updir[2] = 0.;
672 switch (ud) {
673 case UDzneg:
674 updir[2] = -1.;
675 break;
676 case UDyneg:
677 updir[1] = -1.;
678 break;
679 case UDxneg:
680 updir[0] = -1.;
681 break;
682 case UDxpos:
683 updir[0] = 1.;
684 break;
685 case UDypos:
686 updir[1] = 1.;
687 break;
688 case UDzpos:
689 updir[2] = 1.;
690 break;
691 case UDunknown:
692 return(0);
693 }
694 fcross(xdest, updir, nrm);
695 if (normalize(xdest) == 0.0)
696 return(0);
697 fcross(ydest, nrm, xdest);
698 xf(&myxf, addrot(xfargs, xdest, ydest, nrm), xfargs);
699 copymat4(xm, myxf.xfm);
700 if (xfbuf == NULL)
701 return(1);
702 /* return xf arguments as well */
703 for (i = 0; xfargs[i] != NULL; i++) {
704 *xfbuf++ = ' ';
705 strcpy(xfbuf, xfargs[i]);
706 while (*xfbuf) ++xfbuf;
707 }
708 return(1);
709 }