From 1744eae0cb4a6873c2c54bbbd89ee4eb3ac11cae Mon Sep 17 00:00:00 2001 From: "Gregory J. Ward" Date: Mon, 9 Dec 2024 00:44:29 +0000 Subject: [PATCH] feat: Added new WGMDfunc programmable material --- doc/notes/ReleaseNotes | 3 + src/rt/CMakeLists.txt | 1 + src/rt/Rmakefile | 36 +- src/rt/ambient.c | 4 +- src/rt/initotypes.c | 3 +- src/rt/m_wgmdf.c | 627 ++++++++++++++++++++++++++++++++ src/rt/otspecial.h | 23 +- src/rt/raytrace.c | 10 +- src/rt/rtotypes.h | 4 +- src/rt/rv3.c | 6 +- src/rt/srcobstr.c | 4 +- test/renders/Makefile | 24 +- test/renders/combined.rif | 3 +- test/renders/combined_scene.rad | 8 +- test/renders/cylmods.cal | 22 ++ test/renders/gymbal2.rad | 92 +++++ test/renders/ref/cab.rtm | Bin 36116 -> 36125 bytes test/renders/ref/combined.rad | 281 ++++++++++++-- test/renders/ref/wgmdf_def.hdr | Bin 0 -> 54033 bytes test/renders/ringlight.rad | 32 ++ test/renders/wgmdf.rif | 28 ++ 21 files changed, 1125 insertions(+), 86 deletions(-) create mode 100644 src/rt/m_wgmdf.c create mode 100644 test/renders/cylmods.cal create mode 100644 test/renders/gymbal2.rad create mode 100644 test/renders/ref/wgmdf_def.hdr create mode 100644 test/renders/ringlight.rad create mode 100644 test/renders/wgmdf.rif diff --git a/doc/notes/ReleaseNotes b/doc/notes/ReleaseNotes index 02eb43bb1..831207377 100644 --- a/doc/notes/ReleaseNotes +++ b/doc/notes/ReleaseNotes @@ -2761,3 +2761,6 @@ to include this option. Added strnstr.c compatibility module in ray/src/common, since it isn't present on Linux-derivatives. + +Added WGMDfunc material type with programmable roughness and +separate modifier paths for the different components. diff --git a/src/rt/CMakeLists.txt b/src/rt/CMakeLists.txt index 211fef8cb..eb364cb03 100644 --- a/src/rt/CMakeLists.txt +++ b/src/rt/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(radiance m_alias.c m_brdf.c m_bsdf.c + m_wgmdf.c m_clip.c m_direct.c m_mirror.c diff --git a/src/rt/Rmakefile b/src/rt/Rmakefile index fd6694f76..0822b4882 100644 --- a/src/rt/Rmakefile +++ b/src/rt/Rmakefile @@ -1,4 +1,4 @@ -# RCSid: $Id: Rmakefile,v 2.99 2024/11/15 20:47:42 greg Exp $ +# RCSid: $Id: Rmakefile,v 2.100 2024/12/09 00:44:29 greg Exp $ # # Compiles for ray tracing programs. # @@ -95,10 +95,10 @@ SURFSRC = sphere.c source.c srcobstr.c srcsupp.c srcsamp.c virtuals.c \ MATOBJS = aniso.o normal.o dielectric.o m_clip.o glass.o m_brdf.o \ m_mirror.o m_direct.o m_mist.o fprism.o m_alias.o m_bsdf.o \ - ashikhmin.o + m_wgmdf.o ashikhmin.o MATSRC = aniso.c normal.c dielectric.c m_clip.c glass.c m_brdf.c \ m_mirror.c m_direct.c m_mist.c fprism.c m_alias.c m_bsdf.c \ - ashikhmin.c + m_wgmdf.c ashikhmin.c MODOBJS = p_func.o t_func.o p_data.o t_data.o text.o mx_func.o mx_data.o MODSRC = p_func.c t_func.c p_data.c t_data.c text.c mx_func.c mx_data.c @@ -233,13 +233,14 @@ rtrace.o rvmain.o rv2.o rv3.o: ../common/octree.h o_instance.o: ../common/instance.h ambient.o aniso.o ashikhmin.o dielectric.o freeobjmem.o func.o glass.o \ -initotypes.o m_brdf.o m_bsdf.o m_direct.o m_mirror.o normal.o o_cone.o \ -preload.o raycalls.o raytrace.o rtrace.o rv2.o source.o sphere.o \ -srcsupp.o text.o srcdraw.o srcobstr.o virtuals.o: ../common/otypes.h +initotypes.o m_brdf.o m_bsdf.o m_wgmdf.o m_direct.o m_mirror.o normal.o \ +o_cone.o preload.o raycalls.o raytrace.o rtrace.o rv2.o source.o sphere.o \ +m_wgmdf.o srcsupp.o text.o srcdraw.o srcobstr.o \ +virtuals.o: ../common/otypes.h ambient.o ambcomp.o aniso.o ashikhmin.o normal.o p_func.o raycalls.o raytrace.o \ rpict.o rvmain.o rtmain.o rpmain.o rcmain.o rxpiece.o persist.o source.o rv3.o \ -srcsamp.o virtuals.o: ../common/random.h +srcsamp.o virtuals.o m_wgmdf.o: ../common/random.h ambcomp.o ambient.o aniso.o ashikhmin.o dielectric.o freeobjmem.o func.o \ glass.o m_bsdf.o m_brdf.o m_clip.o m_direct.o m_mirror.o m_mist.o mx_data.o \ @@ -248,7 +249,7 @@ raycalls.o raypcalls.o rayfifo.o raytrace.o rpict.o rtrace.o rv2.o rv3.o rview.o source.o sphere.o srcdraw.o srcobstr.o srcsamp.o srcsupp.o t_data.o t_func.o \ text.o rpmain.o rtmain.o rvmain.o virtuals.o m_alias.o rcmain.o \ rcontrib.o rc2.o rc3.o RtraceSimulManager.o RpictSimulManager.o \ -rxtrace.o rxtmain.o rxpiece.o rxpmain.o rxcmain.o \ +rxtrace.o rxtmain.o rxpiece.o rxpmain.o rxcmain.o m_wgmdf.o \ RcontribSimulManager.o RpictSimulManager.o RtraceSimulManager.o: ray.h \ ../common/standard.h ../common/rtmisc.h ../common/rtio.h ../common/rtmath.h \ ../common/rterror.h ../common/octree.h \ @@ -256,7 +257,7 @@ RcontribSimulManager.o RpictSimulManager.o RtraceSimulManager.o: ray.h \ rv2.o rv3.o rview.o: rpaint.h driver.h ../common/view.h ../common/resolu.h -m_direct.o m_mirror.o m_mist.o dielectric.o raycalls.o \ +m_direct.o m_mirror.o m_mist.o raycalls.o \ rpict.o rpmain.o rtmain.o rvmain.o source.o srcdraw.o \ srcobstr.o srcsamp.o srcsupp.o virtuals.o: source.h @@ -308,7 +309,11 @@ aniso.o ashikhmin.o dielectric.o freeobjmem.o glass.o initotypes.o \ m_alias.o m_brdf.o m_clip.o m_direct.o m_mirror.o m_mist.o \ mx_data.o mx_func.o normal.o o_cone.o o_face.o o_instance.o \ o_mesh.o p_data.o p_func.o source.o sphere.o t_data.o t_func.o \ -srcobstr.o text.o: rtotypes.h +m_wgmdf.o srcobstr.o text.o: rtotypes.h + +aniso.o ashikhmin.o dielectric.o glass.o m_brdf.o \ +m_direct.o m_mirror.o m_mist.o normal.o m_wgmdf.o \ +srcobstr.o: source.h m_bsdf.o: ambient.h source.h func.h \ ../common/calcomp.h ../common/bsdf.h ../common/random.h @@ -319,9 +324,11 @@ func.h ../common/calcomp.h ../common/rtprocess.h ambient.o rcmain.o: ambient.h -rcmain.o: source.h +rcmain.o rpmain.o rtmain.o rvmain.o rtrace.o \ +rcontrib.o source.o srcdraw.o srcobstr.o \ +srcsamp.o srcsupp.o srcskipload.o virtuals.o: source.h -rcontrib.o: source.h ../common/otypes.h +rcontrib.o: ../common/otypes.h rc2.o: ../common/resolu.h @@ -332,8 +339,9 @@ rc3.o: ../common/selcall.h # ambient.o: pmapparm.h pmaptype.h pmapamb.h pmapdata.h -dielectric.o glass.o normal.o m_brdf.o m_bsdf.o ashikhmin.o aniso.o: \ - pmapparm.h pmaptype.h pmapmat.h pmap.h pmapdata.h +dielectric.o glass.o normal.o m_brdf.o m_bsdf.o \ +m_wgmdf.o ashikhmin.o aniso.o: pmapparm.h pmaptype.h \ +pmapmat.h pmap.h pmapdata.h raycalls.o rpmain.o rcmain.o rtmain.o rvmain.o: \ pmapparm.h pmaptype.h pmapray.h diff --git a/src/rt/ambient.c b/src/rt/ambient.c index c9c01002d..16473f0cb 100644 --- a/src/rt/ambient.c +++ b/src/rt/ambient.c @@ -1,4 +1,4 @@ -static const char RCSid[] = "$Id: ambient.c,v 2.125 2024/11/11 19:01:55 greg Exp $"; +static const char RCSid[] = "$Id: ambient.c,v 2.126 2024/12/09 00:44:29 greg Exp $"; /* * ambient.c - routines dealing with ambient (inter-reflected) component. * @@ -382,7 +382,7 @@ plugaleak(RAY *r, AMBVAL *ap, FVECT anorm, double ang) rtst.rmax = normalize(rtst.rdir); /* short ray test */ while (localhit(&rtst, &thescene)) { /* check for occluder */ OBJREC *m = findmaterial(rtst.ro); - if (m != NULL && !istransp(m->otype) && !isBSDFproxy(m) && + if (m != NULL && !istransp(m) && !isBSDFproxy(m) && (rtst.clipset == NULL || !inset(rtst.clipset, rtst.ro->omod))) return(1); /* plug light leak */ diff --git a/src/rt/initotypes.c b/src/rt/initotypes.c index 2cce590ac..1e4a16f39 100644 --- a/src/rt/initotypes.c +++ b/src/rt/initotypes.c @@ -1,5 +1,5 @@ #ifndef lint -static const char RCSid[] = "$Id: initotypes.c,v 2.32 2023/12/13 23:26:16 greg Exp $"; +static const char RCSid[] = "$Id: initotypes.c,v 2.33 2024/12/09 00:44:29 greg Exp $"; #endif /* * Initialize ofun[] list for renderers @@ -58,6 +58,7 @@ initotypes(void) /* initialize ofun array */ ofun[MAT_MIST].flags |= T_TRANSP; ofun[MAT_GLASS].funp = m_glass; ofun[MAT_GLASS].flags |= T_TRANSP; + ofun[MAT_WGMDF].funp = m_wgmdf; ofun[MAT_MIRROR].funp = m_mirror; ofun[MAT_DIRECT1].funp = ofun[MAT_DIRECT2].funp = m_direct; diff --git a/src/rt/m_wgmdf.c b/src/rt/m_wgmdf.c new file mode 100644 index 000000000..6c1cd08a2 --- /dev/null +++ b/src/rt/m_wgmdf.c @@ -0,0 +1,627 @@ +#ifndef lint +static const char RCSid[] = "$Id: m_wgmdf.c,v 2.1 2024/12/09 00:44:29 greg Exp $"; +#endif +/* + * Shading function for programmable Ward-Geisler-Moroder-Duer material. + */ + +#include "copyright.h" + +#include "ray.h" +#include "ambient.h" +#include "otypes.h" +#include "rtotypes.h" +#include "source.h" +#include "func.h" +#include "random.h" +#include "pmapmat.h" + +#ifndef MAXITER +#define MAXITER 10 /* maximum # specular ray attempts */ +#endif + /* estimate of Fresnel function */ +#define FRESNE(ci) (exp(-5.85*(ci)) - 0.00202943064) +#define FRESTHRESH 0.017999 /* minimum specularity for approx. */ + +/* + * This routine implements the anisotropic Gaussian + * model described by Ward in a 1992 Siggraph article and updated by + * Geisler-Moroder and Duer in a 2010 article in High Performance Graphics. + * We do not reorient incoming ray, using side in part to determine + * reflectance values. Most parameters are programmable with their own + * modifiers and/or value expressions. + * + * Arguments for MAT_WGMDF are: + * 13+ rs_mod rs rs_urough rs_vrough + * ts_mod ts ts_urough ts_vrough + * td_mod + * ux uy uz funcfile transform + * 0 + * 9+ rfdif gfdif bfdif + * rbdif gbdif bbdif + * rtdif gtdif btdif + * A10 .. + * + * Where the rs_urough or rs_vrough expression yields zero, mirror-Fresnel + * effects are computed, similar to MAT_PLASTIC and MAT_METAL. The + * rs* expressions should not vary with incident angle, or the material + * will not be physically valid. Similarly, the ts* expressions should + * give the same value for coincident direction vectors from either side. + * There are independent modifiers for specular reflection, + * transmission, and diffuse transmission. Diffuse reflection + * applies the material's main modifier, which doesn't apply to + * anything else by default. However, any of the modifiers may be + * ALIASMOD, which will use the main material modifier, or VOIDID, + * which will just be white. + * Diffuse reflection and transmission colors and patterns add to + * the specular components, and are only adjusted with mirror-Fresnel + * reflection if specular reflection is greater than FRESHTHRESH. The + * specular transmission is likewise adjusted in such cases. Specified + * values for all components should sum to less than 1, but like other + * Radiance materials, this is not enforced, nor is a warning issued. + */ + /* specularity flags */ +#define SP_REFL 01 /* has reflected specular component */ +#define SP_TRAN 02 /* has transmitted specular */ +#define SP_RPURE 04 /* mirror reflection */ +#define SP_TPURE 010 /* has view component */ +#define SP_FLAT 020 /* flat reflecting surface */ +#define SP_RBLT 040 /* reflection below sample threshold */ +#define SP_TBLT 0100 /* transmission below threshold */ + +typedef struct { + char *nam; /* modifier name */ + int hastexture; /* has a texture? */ + FVECT pnorm; /* perturbed normal direction */ + double pdot; /* perturbed dot product */ + SCOLOR pcol; /* pattern color */ +} MODVAL; /* modifier-derived values */ + +typedef struct { + MODVAL mo; /* modifier parameters */ + SCOLOR scol; /* modified diffuse color */ +} DCOMP; /* diffuse component parameters */ + +typedef struct { + MODVAL mo; /* modifier parameters */ + SCOLOR scol; /* modified specular color */ + FVECT u, v; /* u and v in-plane vectors */ + double u_alpha; /* u roughness */ + double v_alpha; /* v roughness */ +} SCOMP; /* specular component parameters */ + +typedef struct { + RAY *rp; /* ray pointer */ + OBJREC *mtp; /* material pointer */ + MFUNC *mf; /* pointer to expression list */ + int specfl; /* specularity flags, defined above */ + FVECT ulocal; /* u-vector in local coordinates */ + DCOMP rd, td; /* diffuse component params */ + SCOMP rs, ts; /* specular component params */ + FVECT prdir; /* vector in transmitted direction */ +} WGMDDAT; /* WGMD material data */ + +#define clr_comps(wp) ((wp)->specfl = 0, \ + (wp)->rd.mo.nam = (wp)->td.mo.nam = \ + (wp)->rs.mo.nam = (wp)->ts.mo.nam = "") + +/* assign modifier values */ +static int +set_modval(MODVAL *mp, OBJECT omod, const RAY *r) +{ + RAY tr; + + if (!mp->nam[0]) + mp->nam = (omod == OVOID) ? VOIDID : objptr(omod)->oname; + else if (!strcmp(mp->nam, VOIDID)) + omod = OVOID; + else if (omod == OVOID) + return(0); + tr = *r; /* independent modifier */ + raytexture(&tr, omod); + if (DOT(tr.pert,tr.pert) > FTINY*FTINY) { + mp->pdot = raynormal(mp->pnorm, &tr); + mp->hastexture = 1; + } else { + VCOPY(mp->pnorm, tr.ron); + mp->pdot = tr.rod; + mp->hastexture = 0; + } + copyscolor(mp->pcol, tr.pcol); + return(1); +} + +/* fill modifier values, using previous setting if found */ +static int +fill_modval(MODVAL *mp, const WGMDDAT *wp) +{ + if (mp == &wp->rd.mo) { /* special case (should be first) */ + set_modval(mp, wp->mtp->omod, wp->rp); + return(1); + } /* use main modifier? */ + if (!strcmp(mp->nam, ALIASMOD) || !strcmp(mp->nam, wp->rd.mo.nam)) { + *mp = wp->rd.mo; + return(1); + } /* check others */ + if (mp != &wp->td.mo && !strcmp(mp->nam, wp->td.mo.nam)) { + *mp = wp->td.mo; + return(1); + } + if (mp != &wp->rs.mo && !strcmp(mp->nam, wp->rs.mo.nam)) { + *mp = wp->rs.mo; + return(1); + } + if (mp != &wp->ts.mo && !strcmp(mp->nam, wp->ts.mo.nam)) { + *mp = wp->ts.mo; + return(1); + } /* new modifier */ + return(set_modval(mp, lastmod(objndx(wp->mtp), mp->nam), wp->rp)); +} + +/* assign indicated diffuse component (do !trans first) */ +static void +set_dcomp(WGMDDAT *wp, int trans) +{ + DCOMP *dp = trans ? &wp->td : &wp->rd; + const int offs = trans ? 6 : 3*(wp->rp->rod < 0); + + if (trans) { /* transmitted diffuse? */ + if (intens(wp->mtp->oargs.farg+offs) <= FTINY) { + scolorblack(dp->scol); + return; + } + dp->mo.nam = wp->mtp->oargs.sarg[8]; + if (!fill_modval(&dp->mo, wp)) { + sprintf(errmsg, + "unknown diffuse transmission modifier '%s'", + dp->mo.nam); + objerror(wp->mtp, USER, errmsg); + } + } else /* no priors for main mod */ + fill_modval(&dp->mo, wp); + + setscolor(dp->scol, wp->mtp->oargs.farg[offs], + wp->mtp->oargs.farg[offs+1], + wp->mtp->oargs.farg[offs+2]); + smultscolor(dp->scol, dp->mo.pcol); +} + +/* assign indicated specular component */ +static void +set_scomp(WGMDDAT *wp, int trans) +{ + SCOMP *sp = trans ? &wp->ts : &wp->rs; + const int eoff = 3*(trans != 0); + double coef; + + setfunc(wp->mtp, wp->rp); /* get coefficient, first */ + errno = 0; + coef = evalue(wp->mf->ep[eoff]); + if ((errno == EDOM) | (errno == ERANGE)) { + objerror(wp->mtp, WARNING, "specular compute error"); + scolorblack(sp->scol); + return; + } + if (coef <= FTINY) { /* negligible value? */ + scolorblack(sp->scol); + return; + } /* else get modifier */ + sp->mo.nam = wp->mtp->oargs.sarg[4*(trans != 0)]; + if (!fill_modval(&sp->mo, wp)) { + sprintf(errmsg, "unknown specular %s modifier '%s'", + trans ? "transmission" : "reflection", sp->mo.nam); + objerror(wp->mtp, USER, errmsg); + } + copyscolor(sp->scol, sp->mo.pcol); + scalescolor(sp->scol, coef); + if (sintens(sp->scol) <= FTINY) { + scolorblack(sp->scol); + return; /* got black pattern */ + } + setfunc(wp->mtp, wp->rp); /* else get roughness */ + errno = 0; + sp->u_alpha = evalue(wp->mf->ep[eoff+1]); + sp->v_alpha = (sp->u_alpha > FTINY) ? evalue(wp->mf->ep[eoff+2]) : 0.0; + if ((errno == EDOM) | (errno == ERANGE)) { + objerror(wp->mtp, WARNING, "roughness compute error"); + scolorblack(sp->scol); + return; + } /* we have something... */ + wp->specfl |= trans ? SP_TRAN : SP_REFL; + if (sp->v_alpha <= FTINY) { /* is it pure specular? */ + wp->specfl |= trans ? SP_TPURE : SP_RPURE; + sp->u_alpha = sp->v_alpha = 0.0; + return; + } + /* get anisotropic coordinates */ + fcross(sp->v, sp->mo.pnorm, wp->ulocal); + if (normalize(sp->v) == 0.0) { /* orientation vector==normal? */ + if (fabs(sp->u_alpha - sp->v_alpha) > 0.001) + objerror(wp->mtp, WARNING, "bad orientation vector"); + getperpendicular(sp->u, sp->mo.pnorm, 1); /* punting */ + fcross(sp->v, sp->mo.pnorm, sp->u); + sp->u_alpha = sp->v_alpha = sqrt( 0.5 * + (sp->u_alpha*sp->u_alpha + sp->v_alpha*sp->v_alpha) ); + } else + fcross(sp->u, sp->v, sp->mo.pnorm); +} + +/* sample anisotropic Gaussian specular */ +static void +agaussamp(WGMDDAT *wp) +{ + RAY sr; + FVECT h; + double rv[2]; + double d, sinp, cosp; + int maxiter, ntrials, nstarget, nstaken; + int i; + /* compute reflection */ + if ((wp->specfl & (SP_REFL|SP_RPURE|SP_RBLT)) == SP_REFL && + rayorigin(&sr, RSPECULAR, wp->rp, wp->rs.scol) == 0) { + SCOLOR scol; + nstarget = 1; + if (specjitter > 1.5) { /* multiple samples? */ + nstarget = specjitter*wp->rp->rweight + .5; + if (sr.rweight <= minweight*nstarget) + nstarget = sr.rweight/minweight; + if (nstarget > 1) { + d = 1./nstarget; + scalescolor(sr.rcoef, d); + sr.rweight *= d; + } else + nstarget = 1; + } + scolorblack(scol); + dimlist[ndims++] = (int)(size_t)wp->mtp; + maxiter = MAXITER*nstarget; + for (nstaken = ntrials = 0; (nstaken < nstarget) & + (ntrials < maxiter); ntrials++) { + if (ntrials) + d = frandom(); + else + d = urand(ilhash(dimlist,ndims)+samplendx); + multisamp(rv, 2, d); + d = 2.0*PI * rv[0]; + cosp = tcos(d) * wp->rs.u_alpha; + sinp = tsin(d) * wp->rs.v_alpha; + d = 1./sqrt(cosp*cosp + sinp*sinp); + cosp *= d; + sinp *= d; + if ((0. <= specjitter) & (specjitter < 1.)) + rv[1] = 1.0 - specjitter*rv[1]; + d = (rv[1] <= FTINY) ? 1.0 : sqrt( -log(rv[1]) / + (cosp*cosp/(wp->rs.u_alpha*wp->rs.u_alpha) + + sinp*sinp/(wp->rs.v_alpha*wp->rs.v_alpha)) ); + for (i = 0; i < 3; i++) + h[i] = wp->rs.mo.pnorm[i] + + d*(cosp*wp->rs.u[i] + sinp*wp->rs.v[i]); + d = -2.0 * DOT(h, wp->rp->rdir) / (1.0 + d*d); + VSUM(sr.rdir, wp->rp->rdir, h, d); + /* sample rejection test */ + d = DOT(sr.rdir, wp->rp->ron); + if ((d > 0) ^ (wp->rp->rod > 0)) + continue; + checknorm(sr.rdir); + if (nstarget > 1) { /* W-G-M-D adjustment */ + if (nstaken) rayclear(&sr); + rayvalue(&sr); + d = 2./(1. + wp->rp->rod/d); + scalescolor(sr.rcol, d); + saddscolor(scol, sr.rcol); + } else { + rayvalue(&sr); + smultscolor(sr.rcol, sr.rcoef); + saddscolor(wp->rp->rcol, sr.rcol); + } + ++nstaken; + } + if (nstarget > 1) { /* final W-G-M-D weighting */ + smultscolor(scol, sr.rcoef); + d = (double)nstarget/ntrials; + scalescolor(scol, d); + saddscolor(wp->rp->rcol, scol); + } + ndims--; + } + /* compute transmission */ + if ((wp->specfl & (SP_TRAN|SP_TPURE|SP_TBLT)) == SP_TRAN && + rayorigin(&sr, TSPECULAR, wp->rp, wp->ts.scol) == 0) { + nstarget = 1; + if (specjitter > 1.5) { /* multiple samples? */ + nstarget = specjitter*wp->rp->rweight + .5; + if (sr.rweight <= minweight*nstarget) + nstarget = sr.rweight/minweight; + if (nstarget > 1) { + d = 1./nstarget; + scalescolor(sr.rcoef, d); + sr.rweight *= d; + } else + nstarget = 1; + } + dimlist[ndims++] = (int)(size_t)wp->mtp; + maxiter = MAXITER*nstarget; + for (nstaken = ntrials = 0; (nstaken < nstarget) & + (ntrials < maxiter); ntrials++) { + if (ntrials) + d = frandom(); + else + d = urand(ilhash(dimlist,ndims)+1823+samplendx); + multisamp(rv, 2, d); + d = 2.0*PI * rv[0]; + cosp = tcos(d) * wp->ts.u_alpha; + sinp = tsin(d) * wp->ts.v_alpha; + d = 1./sqrt(cosp*cosp + sinp*sinp); + cosp *= d; + sinp *= d; + if ((0. <= specjitter) & (specjitter < 1.)) + rv[1] = 1.0 - specjitter*rv[1]; + if (rv[1] <= FTINY) + d = 1.0; + else + d = sqrt(-log(rv[1]) / + (cosp*cosp/(wp->ts.u_alpha*wp->ts.u_alpha) + + sinp*sinp/(wp->ts.v_alpha*wp->ts.v_alpha))); + for (i = 0; i < 3; i++) + sr.rdir[i] = wp->prdir[i] + + d*(cosp*wp->ts.u[i] + sinp*wp->ts.v[i]); + /* rejection test */ + if ((DOT(sr.rdir,wp->rp->ron) > 0) == (wp->rp->rod > 0)) + continue; + normalize(sr.rdir); /* OK, normalize */ + if (nstaken) /* multi-sampling? */ + rayclear(&sr); + rayvalue(&sr); + smultscolor(sr.rcol, sr.rcoef); + saddscolor(wp->rp->rcol, sr.rcol); + ++nstaken; + } + ndims--; + } +} + +/* compute source contribution for MAT_WGMDF */ +static void +dirwgmdf(SCOLOR scval, void *uwp, FVECT ldir, double omega) +{ + WGMDDAT *wp = (WGMDDAT *)uwp; + const int hitfront = (wp->rp->rod > 0); + double fresadj = 1.; + double ldot; + double dtmp, dtmp1, dtmp2; + FVECT h; + double au2, av2; + SCOLOR sctmp; + + scolorblack(scval); /* will add component coefficients */ + + /* XXX ignores which side is lit */ + if (wp->specfl & SP_RPURE && pbright(wp->rs.scol) >= FRESTHRESH) + fresadj = 1. - FRESNE(fabs(DOT(wp->rs.mo.pnorm,ldir))); + + if (sintens(wp->rd.scol) > FTINY && + ((ldot = DOT(wp->rd.mo.pnorm,ldir)) > 0) == hitfront) { + /* + * Compute diffuse reflection coefficient for source. + */ + copyscolor(sctmp, wp->rd.scol); + dtmp = fabs(ldot) * omega * (1.0/PI) * fresadj; + scalescolor(sctmp, dtmp); + saddscolor(scval, sctmp); + } + if (sintens(wp->td.scol) > FTINY && + ((ldot = DOT(wp->td.mo.pnorm,ldir)) > 0) ^ hitfront) { + /* + * Compute diffuse transmission coefficient for source. + */ + copyscolor(sctmp, wp->td.scol); + dtmp = fabs(ldot) * omega * (1.0/PI) * fresadj; + scalescolor(sctmp, dtmp); + saddscolor(scval, sctmp); + } +#if 0 /* XXX not yet implemented */ + if (ambRayInPmap(wp->rp)) + return; /* specular accounted for in photon map */ +#endif + if ((wp->specfl & (SP_REFL|SP_RPURE)) == SP_REFL && + ((ldot = DOT(wp->rs.mo.pnorm,ldir)) > 0) == hitfront) { + /* + * Compute specular reflection coefficient for source using + * anisotropic Gaussian distribution model. + */ + /* add source width if flat */ + if (wp->specfl & SP_FLAT) + au2 = av2 = omega * (0.25/PI); + else + au2 = av2 = 0.0; + au2 += wp->rs.u_alpha*wp->rs.u_alpha; + av2 += wp->rs.v_alpha*wp->rs.v_alpha; + /* half vector */ + VSUB(h, ldir, wp->rp->rdir); + /* ellipse */ + dtmp1 = DOT(wp->rs.u, h); + dtmp1 *= dtmp1 / au2; + dtmp2 = DOT(wp->rs.v, h); + dtmp2 *= dtmp2 / av2; + /* W-G-M-D model */ + dtmp = DOT(wp->rs.mo.pnorm, h); + dtmp *= dtmp; + dtmp1 = (dtmp1 + dtmp2) / dtmp; + dtmp = exp(-dtmp1) * DOT(h,h) / + (PI * dtmp*dtmp * sqrt(au2*av2)); + + if (dtmp > FTINY) { /* worth using? */ + copyscolor(sctmp, wp->rs.scol); + dtmp *= fabs(ldot) * omega; + scalescolor(sctmp, dtmp); + saddscolor(scval, sctmp); + } + } + if ((wp->specfl & (SP_TRAN|SP_TPURE)) == SP_TRAN && + ((ldot = DOT(wp->ts.mo.pnorm,ldir)) > 0) ^ hitfront) { + /* + * Compute specular transmission coefficient for source. + */ + /* roughness + source */ + au2 = av2 = omega * (1.0/PI); + au2 += wp->ts.u_alpha*wp->ts.u_alpha; + av2 += wp->ts.v_alpha*wp->ts.v_alpha; + /* "half vector" */ + VSUB(h, ldir, wp->prdir); + dtmp = DOT(h,h); + if (dtmp > FTINY*FTINY) { + dtmp1 = DOT(h,wp->ts.mo.pnorm); + dtmp = 1.0 - dtmp1*dtmp1/dtmp; + } + if (dtmp > FTINY*FTINY) { + dtmp1 = DOT(h,wp->ts.u); + dtmp1 *= dtmp1 / au2; + dtmp2 = DOT(h,wp->ts.v); + dtmp2 *= dtmp2 / av2; + dtmp = (dtmp1 + dtmp2) / dtmp; + dtmp = exp(-dtmp); + } else + dtmp = 1.0; + /* Gaussian */ + dtmp *= (1.0/PI) * sqrt(-ldot/(wp->ts.mo.pdot*au2*av2)); + + if (dtmp > FTINY) { /* worth using? */ + copyscolor(sctmp, wp->ts.scol); + dtmp *= omega; + scalescolor(sctmp, dtmp); + saddscolor(scval, sctmp); + } + } +} + +/* color a ray that hit a programmable WGMD material */ +int +m_wgmdf(OBJREC *m, RAY *r) +{ + RAY lr; + WGMDDAT wd; + SCOLOR sctmp; + FVECT anorm; + int i; + + if (!backvis & (r->rod < 0.0)) { + raytrans(r); + return(1); /* backside invisible */ + } + if ((m->oargs.nsargs < 13) | (m->oargs.nfargs < 9)) + objerror(m, USER, "bad number of arguments"); + clr_comps(&wd); + wd.rp = r; + wd.mtp = m; + wd.mf = getfunc(m, 12, 0xEEE, 1); + setfunc(m, r); /* get local u vector */ + errno = 0; + for (i = 0; i < 3; i++) + wd.ulocal[i] = evalue(wd.mf->ep[6+i]); + if ((errno == EDOM) | (errno == ERANGE)) + wd.ulocal[0] = wd.ulocal[1] = wd.ulocal[2] = 0.0; + else if (wd.mf->fxp != &unitxf) + multv3(wd.ulocal, wd.ulocal, wd.mf->fxp->xfm); + + set_dcomp(&wd, 0); /* call this first */ + set_dcomp(&wd, 1); /* call this second */ + set_scomp(&wd, 1); /* sets SP_TPURE */ + if (r->crtype & SHADOW && !(wd.specfl & SP_TPURE)) + return(1); /* early shadow test */ + set_scomp(&wd, 0); + wd.specfl |= SP_FLAT*(r->ro != NULL && isflat(r->ro->otype)); + /* apply Fresnel adjustments? */ + if (wd.specfl & SP_RPURE && pbright(wd.rs.scol) >= FRESTHRESH) { + const double fest = FRESNE(fabs(wd.rs.mo.pdot)); + for (i = NCSAMP; i--; ) + wd.rs.scol[i] += fest*(1. - wd.rs.scol[i]); + scalescolor(wd.ts.scol, 1.-fest); + scalescolor(wd.td.scol, 1.-fest); + } + /* check specular thresholds */ + wd.specfl |= SP_RBLT*((wd.specfl & (SP_REFL|SP_RPURE)) == SP_REFL && + specthresh >= pbright(wd.rs.scol)-FTINY); + wd.specfl |= SP_TBLT*((wd.specfl & (SP_TRAN|SP_TPURE)) == SP_TRAN && + specthresh >= pbright(wd.ts.scol)-FTINY); + /* get through direction */ + if (wd.specfl & SP_TRAN && wd.ts.mo.hastexture && + !(r->crtype & (SHADOW|AMBIENT))) { + for (i = 0; i < 3; i++) /* perturb */ + wd.prdir[i] = r->rdir[i] - wd.ts.mo.pnorm[i] + r->ron[i]; + if ((DOT(wd.prdir,r->ron) > 0) ^ (r->rod > 0)) + normalize(wd.prdir); /* OK */ + else /* too much */ + VCOPY(wd.prdir, r->rdir); + } else + VCOPY(wd.prdir, r->rdir); + /* transmitted view ray? */ + if ((wd.specfl & (SP_TRAN|SP_TPURE|SP_TBLT)) == (SP_TRAN|SP_TPURE) && + rayorigin(&lr, TRANS, r, wd.ts.scol) == 0) { + VCOPY(lr.rdir, wd.prdir); + rayvalue(&lr); + smultscolor(lr.rcol, lr.rcoef); + saddscolor(r->rcol, lr.rcol); + if (scolor_mean(wd.ts.scol) >= 0.999) { + /* completely transparent */ + smultscolor(lr.mcol, lr.rcoef); + copyscolor(r->mcol, lr.mcol); + r->rmt = r->rot + lr.rmt; + r->rxt = r->rot + lr.rxt; + } else if (pbright(wd.ts.scol) > + pbright(wd.td.scol) + pbright(wd.rd.scol)) + r->rxt = r->rot + raydistance(&lr); + } + if (r->crtype & SHADOW) + return(1); /* the rest is shadow */ + /* mirror ray? */ + if ((wd.specfl & (SP_REFL|SP_RPURE|SP_RBLT)) == (SP_REFL|SP_RPURE) && + rayorigin(&lr, REFLECTED, r, wd.rs.scol) == 0) { + VSUM(lr.rdir, r->rdir, wd.rs.mo.pnorm, 2.*wd.rs.mo.pdot); + /* fall back if would penetrate */ + if (wd.rs.mo.hastexture && + (DOT(lr.rdir,r->ron) > 0) ^ (r->rod > 0)) + VSUM(lr.rdir, r->rdir, r->ron, 2.*r->rod); + checknorm(lr.rdir); + rayvalue(&lr); + smultscolor(lr.rcol, lr.rcoef); + copyscolor(r->mcol, lr.rcol); + saddscolor(r->rcol, lr.rcol); + r->rmt = r->rot; + if (wd.specfl & SP_FLAT && + !wd.rs.mo.hastexture | (r->crtype & AMBIENT)) + r->rmt += raydistance(&lr); + } + if (wd.specfl & (SP_REFL|SP_TRAN)) /* specularly scattered rays */ + agaussamp(&wd); /* checks *BLT flags */ + + if (sintens(wd.rd.scol) > FTINY) { /* ambient from this side */ + if (r->rod > 0) { + VCOPY(anorm, wd.rd.mo.pnorm); + } else { + anorm[0] = -wd.rd.mo.pnorm[0]; + anorm[1] = -wd.rd.mo.pnorm[1]; + anorm[2] = -wd.rd.mo.pnorm[2]; + } + copyscolor(sctmp, wd.rd.scol); + if (wd.specfl & SP_RBLT) /* add in specular as well? */ + saddscolor(sctmp, wd.rs.scol); + multambient(sctmp, r, anorm); + saddscolor(r->rcol, sctmp); /* add to returned color */ + } + if (sintens(wd.td.scol) > FTINY) { /* ambient from other side */ + if (r->rod > 0) { + anorm[0] = -wd.td.mo.pnorm[0]; + anorm[1] = -wd.td.mo.pnorm[1]; + anorm[2] = -wd.td.mo.pnorm[2]; + } else { + VCOPY(anorm, wd.td.mo.pnorm); + } + copyscolor(sctmp, wd.td.scol); + if (wd.specfl & SP_TBLT) /* add in specular as well? */ + saddscolor(sctmp, wd.ts.scol) + multambient(sctmp, r, anorm); + saddscolor(r->rcol, sctmp); + } + direct(r, dirwgmdf, &wd); /* add direct component last */ + return(1); +} diff --git a/src/rt/otspecial.h b/src/rt/otspecial.h index 5da3765db..8e75feece 100644 --- a/src/rt/otspecial.h +++ b/src/rt/otspecial.h @@ -1,27 +1,29 @@ -/* RCSid $Id: otspecial.h,v 2.10 2019/04/18 22:37:36 greg Exp $ */ +/* RCSid $Id: otspecial.h,v 2.11 2024/12/09 00:44:29 greg Exp $ */ /* * Special type flags for objects used in rendering. * Depends on definitions in otypes.h */ #ifndef _RAD_OTSPECIAL_H_ #define _RAD_OTSPECIAL_H_ + #ifdef __cplusplus extern "C" { #endif - - /* flag for nominally transparent materials */ + /* test for nominally transparent materials */ #define T_TRANSP T_SP1 +#define istransp(m) (ofun[(m)->otype].flags & T_TRANSP || \ + (((m)->otype==MAT_WGMDF) & ((m)->oargs.nsargs > 5) \ + && strcmp((m)->oargs.sarg[5], "0"))) - /* flag for completely opaque materials */ + /* test for completely opaque materials */ #define T_OPAQUE T_SP2 - -#define istransp(t) (ofun[t].flags & T_TRANSP) - -#define isopaque(t) (ofun[t].flags & T_OPAQUE) +#define isopaque(m) (ofun[(m)->otype].flags & T_OPAQUE || \ + (((m)->otype==MAT_WGMDF) & ((m)->oargs.nsargs > 5) \ + && !strcmp((m)->oargs.sarg[5], "0"))) /* test if we have a BSDF proxy surface */ -#define isBSDFproxy(m) (((m)->otype == MAT_BSDF) & ((m)->oargs.nsargs > 0) &&\ - strcmp((m)->oargs.sarg[0], "0")) +#define isBSDFproxy(m) (((m)->otype==MAT_BSDF) & ((m)->oargs.nsargs > 0) \ + && strcmp((m)->oargs.sarg[0], "0")) /* defined in initotypes.c */ extern OBJREC *findmaterial(OBJREC *o); @@ -29,4 +31,5 @@ extern OBJREC *findmaterial(OBJREC *o); #ifdef __cplusplus } #endif + #endif /* _RAD_OTSPECIAL_H_ */ diff --git a/src/rt/raytrace.c b/src/rt/raytrace.c index 79f57cdeb..5db77cd72 100644 --- a/src/rt/raytrace.c +++ b/src/rt/raytrace.c @@ -1,5 +1,5 @@ #ifndef lint -static const char RCSid[] = "$Id: raytrace.c,v 2.92 2024/12/05 19:23:43 greg Exp $"; +static const char RCSid[] = "$Id: raytrace.c,v 2.93 2024/12/09 00:44:29 greg Exp $"; #endif /* * raytrace.c - routines for tracing and shading rays. @@ -220,7 +220,7 @@ raytirrad( /* irradiance hack */ ) { if (m->otype != MAT_CLIP && ismaterial(m->otype)) { - if (istransp(m->otype) || isBSDFproxy(m)) { + if (istransp(m) || isBSDFproxy(m)) { raytrans(r); return(1); } @@ -550,10 +550,10 @@ rayreject( /* check if candidate hit is worse than current */ return(1); /* old has material, new does not */ } else if (mray == NULL) { return(0); /* new has material, old does not */ - } else if (istransp(mnew->otype)) { - if (!istransp(mray->otype)) + } else if (istransp(mnew)) { + if (!istransp(mray)) return(1); /* new is transparent, old is not */ - } else if (istransp(mray->otype)) { + } else if (istransp(mray)) { return(0); /* old is transparent, new is not */ } if (rod <= 0) { /* check which side we hit */ diff --git a/src/rt/rtotypes.h b/src/rt/rtotypes.h index a561392ec..f98851270 100644 --- a/src/rt/rtotypes.h +++ b/src/rt/rtotypes.h @@ -1,4 +1,4 @@ -/* RCSid $Id: rtotypes.h,v 1.7 2023/12/13 23:26:16 greg Exp $ */ +/* RCSid $Id: rtotypes.h,v 1.8 2024/12/09 00:44:29 greg Exp $ */ /* * External functions implementing Radiance object types */ @@ -16,7 +16,7 @@ extern otype_implf o_sphere, o_face, o_cone, o_instance, o_mesh; extern otype_implf m_aniso, m_dielectric, m_glass, m_alias, m_light, m_normal, m_mist, m_mirror, m_direct, m_clip, m_brdf, m_brdf2, - m_bsdf, m_ashikhmin; + m_bsdf, m_ashikhmin, m_wgmdf; extern otype_implf t_func, t_data; diff --git a/src/rt/rv3.c b/src/rt/rv3.c index 063b839c1..49e5db404 100644 --- a/src/rt/rv3.c +++ b/src/rt/rv3.c @@ -1,5 +1,5 @@ #ifndef lint -static const char RCSid[] = "$Id: rv3.c,v 2.46 2024/12/03 19:36:58 greg Exp $"; +static const char RCSid[] = "$Id: rv3.c,v 2.47 2024/12/09 00:44:29 greg Exp $"; #endif /* * rv3.c - miscellaneous routines for rview. @@ -123,11 +123,11 @@ getinterest( /* get area of interest */ thisray.ro->omod)) m = findmaterial(thisray.ro); if ((m != NULL) & !weakhit && - istransp(m->otype) && + istransp(m) && m->otype != MAT_MIST) { weakhit = 1; VCOPY(weakpt, thisray.rop); - } else if (m != NULL && !istransp(m->otype) && + } else if (m != NULL && !istransp(m) && !isBSDFproxy(m)) break; /* something solid */ VCOPY(thisray.rorg, thisray.rop); diff --git a/src/rt/srcobstr.c b/src/rt/srcobstr.c index 5743e959c..64e42ba93 100644 --- a/src/rt/srcobstr.c +++ b/src/rt/srcobstr.c @@ -1,5 +1,5 @@ #ifndef lint -static const char RCSid[] = "$Id: srcobstr.c,v 2.21 2024/11/17 19:36:09 greg Exp $"; +static const char RCSid[] = "$Id: srcobstr.c,v 2.22 2024/12/09 00:44:29 greg Exp $"; #endif /* * Source occlusion caching routines @@ -309,7 +309,7 @@ srcblocker(RAY *r) m = findmaterial(r->ro); if (m == NULL) return(0); /* no material?! */ - if (!isopaque(m->otype)) + if (!isopaque(m)) return(0); /* material not a reliable blocker */ *srcobstructp(r) = r->robj; /* else record obstructor */ return(1); diff --git a/test/renders/Makefile b/test/renders/Makefile index 0598b5648..38b0026d9 100644 --- a/test/renders/Makefile +++ b/test/renders/Makefile @@ -1,4 +1,4 @@ -# RCSid $Id: Makefile,v 1.40 2024/03/12 15:49:18 greg Exp $ +# RCSid $Id: Makefile,v 1.41 2024/12/09 00:44:29 greg Exp $ # # Render and primitive type testing # @@ -27,7 +27,7 @@ test-dielectric-def test-dielectric-fish test-trans-def \ test-glass-def test-glass-fish test-glass-up \ test-trans-fish test-patterns-def test-patterns-plan \ test-rtrace test-mirror-fish test-obj2mesh \ -test-prism1-fish test-prism2-fish test-spectra-fish +test-prism1-fish test-prism2-fish test-spectra-fish test-wgmdf-def clean: rm -f *.oct *.amb *_ill.dat blinds_ill?.dat *_*.hdr *.unf \ @@ -40,7 +40,7 @@ batch1: test-mixtex-def test-mixtex-fish test-mixtex-plan test-mixtex-rplan \ test-xform test-oconv test-rad test-obj2mesh batch2: test-rfluxmtx test-mist-def test-glass-def test-glass-fish test-glass-up \ -test-spectra-fish +test-spectra-fish test-wgmdf-def batch3: test-inst-def test-inst-fish test-trans2-def test-trans2-fish test-trans2-win \ test-dielectric-def test-dielectric-fish @@ -528,3 +528,21 @@ spectra_fish.hdr: spectra.oct rad -v fish spectra.rif ### End spectra-fish tests + +### Octree for wgmdf + +wgmdf.oct: wgmdf.rif + rad -v 0 wgmdf.rif + +### Reference and test for wgmdf def ### + +test-wgmdf-def: wgmdf_def.hdr + $(RDU_PFILT) wgmdf_def.hdr | $(IMG_CMP) ref/wgmdf_def.hdr - + +ref/wgmdf_def.hdr: wgmdf_def.hdr + $(RDU_PFILT) wgmdf_def.hdr > ref/wgmdf_def.hdr + +wgmdf_def.hdr: wgmdf.oct + rad -v def wgmdf.rif + +### End of wgmdf-def tests diff --git a/test/renders/combined.rif b/test/renders/combined.rif index 763d61d9e..a8371e941 100644 --- a/test/renders/combined.rif +++ b/test/renders/combined.rif @@ -1,4 +1,4 @@ -# RCSid $Id: combined.rif,v 1.7 2023/12/14 01:17:22 greg Exp $ +# RCSid $Id: combined.rif,v 1.8 2024/12/09 00:44:29 greg Exp $ # # All the scenes combined into one to test every primitive # @@ -17,6 +17,7 @@ objects = prism2.rad rect_fixture.rad rect_opening.rad saucer.rad objects = spotcones.rad spotlights.rad sunset_sky.rad objects = torus.rad trans_pane.rad vase.rad woman.rad spectral_objs.rad objects = vase.rtm woman.rtm porsche.octf +objects = ringlight.rad gymbal2.rad oconv = -f diff --git a/test/renders/combined_scene.rad b/test/renders/combined_scene.rad index 3558e1aad..152544109 100644 --- a/test/renders/combined_scene.rad +++ b/test/renders/combined_scene.rad @@ -1,4 +1,4 @@ -# RCSid $Id: combined_scene.rad,v 1.12 2023/11/17 21:22:27 greg Exp $ +# RCSid $Id: combined_scene.rad,v 1.13 2024/12/09 00:44:29 greg Exp $ # # A collection of all our scene bits without the front caps # @@ -22,7 +22,7 @@ !xform -m gold -n prism1 -t 0 10.2 3.2 ./vase.rad !xform -n prism2 -t 0 15.6 3.2 diorama_walls.rad rect_opening.rad prism2.rad !xform -m gold -n prism2 -t 0 15.3 3.2 ./vase.rad -!xform -n tfunc -t 0 0 6.4 basic.mat diorama_walls.rad \ +!xform -n tfunc -t 0 0 6.4 diorama_walls.rad \ rect_opening.rad ball_in_cube.rad illum_tfunc.rad !xform -n trans -t 0 5.2 6.4 diorama_walls.rad rect_opening.rad \ ballcompare.rad spotlights.rad rect_fixture.rad trans_pane.rad @@ -32,5 +32,7 @@ closed_end.rad disks.rad rect_fixture.rad !xform -n mixtex -t 0 0 9.6 mixtex.mat diorama_walls.rad \ glass_pane.rad blinds.rad rect_opening.rad disks.rad constellation.rad -!xform -n spectra -t 0 5.2 9.6 basic.mat spectral.mat gold.mat \ +!xform -n spectra -t 0 5.2 9.6 spectral.mat gold.mat \ diorama_walls.rad closed_end.rad rect_fixture.rad spectral_objs.rad +!xform -n wgmdf -t 0 10.4 9.6 \ + diorama_walls.rad closed_end.rad ringlight.rad gymbal2.rad diff --git a/test/renders/cylmods.cal b/test/renders/cylmods.cal new file mode 100644 index 000000000..5268450e3 --- /dev/null +++ b/test/renders/cylmods.cal @@ -0,0 +1,22 @@ +{ RCSid $Id: cylmods.cal,v 1.1 2024/12/09 00:44:29 greg Exp $ } +{ + Unit cylinder functions (centered on Z-axis) + for testing +} +posAngle(a):if(a,a,a+2*PI); + +Cx = posAngle(atan2(Py,Px)); +Cy = Pz; +Rx(n) = n/(2*PI) * Cx; +Ry(n) = n/(2*PI) * Cy; +Sx(n) = .5*(Rx(n) - Ry(n)); +Sy(n) = .5*(Rx(n) + Ry(n)); + + { Checkerboard pattern } +Check(n) = xor(frac(Rx(n))-.5, frac(Ry(n))-.5); + + { Pyramid texture } +Pyramid_dxy(n) = if(frac(Rx(2*n))-.5, .2, -.2); +Pyramid_dx(n) = -Py*Pyramid_dxy(n); +Pyramid_dy(n) = Px*Pyramid_dxy(n); +Pyramid_dz(n) = if(frac(Ry(2*n))-.5, .2, -.2); diff --git a/test/renders/gymbal2.rad b/test/renders/gymbal2.rad new file mode 100644 index 000000000..d7e38d2fa --- /dev/null +++ b/test/renders/gymbal2.rad @@ -0,0 +1,92 @@ +# RCSid $Id: gymbal2.rad,v 1.1 2024/12/09 00:44:29 greg Exp $ +# +# Tests of WGMDfunc material +# + +void colorfunc check1 +6 if(Check(12),.5,.1) if(Check(12),.1,.7) if(Check(12),.4,.9) + cylmods.cal -ry 90 +0 +0 + +void WGMDfunc wgmd_mat1 +15 + void .08 if(Check(8),.005,.07) if(Check(8),.07,.005) + check1 .2 0 0 + void + 0 0 1 + cylmods.cal -ry 90 +0 +9 + .05 .01 .05 + .05 .2 .05 + 0 0 0 + +void texfunc pyramid2 +8 Pyramid_dx(13) Pyramid_dy(13) Pyramid_dz(13) + cylmods.cal -s .9 -rx 90 +0 +0 + +void colorfunc check2 +8 if(Check(9),.9,.1) .1 if(Check(9),.3,.1) + cylmods.cal -s .9 -rx 90 +0 +0 + +void WGMDfunc wgmd_mat2 +13 + pyramid2 .08 0 0 + check2 .2 .02 .02 + void + 0 0 0 + . +0 +9 + 0 0 0 + 0 0 0 + 0 0 0 + +void colorfunc check3 +6 if(Check(7),.9,.1) .1 if(Check(7),.3,.1) + cylmods.cal -s .8 +0 +0 + +check3 WGMDfunc wgmd_mat3 +13 + void .03 .08 .03 + void 0 0 0 + inherit + 0 0 1 + . +0 +9 + 0 0 0 + .4 .1 .1 + .3 .3 .4 + + +wgmd_mat1 cylinder cyl1 +0 +0 +7 + -4.4 2.5 1.1 + -3.6 2.5 1.1 + 1 + +wgmd_mat2 tube tube2 +0 +0 +7 + -4 2.1 1.1 + -4 2.9 1.1 + .9 + +wgmd_mat3 cylinder cyl3 +0 +0 +7 + -4 2.5 0.7 + -4 2.5 1.5 + .8 diff --git a/test/renders/ref/cab.rtm b/test/renders/ref/cab.rtm index 5ac67e1451d102d63e8b1e06622d3b773d02b51f..63df138b137c16412e207b038db89a73076dc92c 100644 GIT binary patch delta 23 fcmbO-i)rpGrVY32Il|q2UD8VPk~iO~x8el=cMS=u delta 14 WcmbO`i)qR%rVY32H$SPjV$_ikgr;D$@=IAvnTiJdr(Q2nit&5{NE(@4ffl!GLXC^Sx{1%$fVg{p-&B0>&8jTI*SFd*0{WEBAeV;Lt}0 zjvjpf%^MeQT)*#j;ryK+E^fJY{;u1lA8y<}f9?EPzw=kG-q~{d{3W-GS8v?;?$X)s zu3sX)ynOq{^}A;;p1(wDS62eAT{wT$1O55U+c$r>c-L+7{hMx^ zJ+^qe@7TH1&1;LdhmXe&H;*mbJv?`AgKu7P+q`Ydwr%d)eb67gy>@zf5P#$DV?zFZh1ItIwBix@~j!@N(OH{GGEI*Dk&mUsR>`t94c{l7kLzIxjYo_y8MZQFO7VI}uA!V}XVwbaQuq^^Iene{|%)=O4X)+!s#M+shrEy~Wd$_~fyZ_~h+QeA>31_~Z^> z692^0^VO#}4}bdkkpm}uJv}yG_~Gu^+gHCkd;7|T@7}z5`G>1;{a_>Cxow1VzwNdK zF!XoZ3P=RN$2Z^q>e#2pzW@Z_-oZ1z+vvXSKYoa~BK~gM;I_g)CqCUg@3!sDH#eVh zBmRf}YU5Y`{_~r^`qlq~VJ|5(28+WJh@@d!V~oWTV~&n8==HkD@Gzx9E|W^cVsWUD z$7WEmmoJ_^9PetZD$Yr@Tl8uP7k@I|SyPZ2r&I9ph0*rv!c3>lVltUytnm(KdRAU> zMQu}i_rS>b)Pwm)3r{J0sX`g1)VQF?7; zWJGwFLI#fy6^TSb0guC=VIfZz9!?Ln)|BR@Ct8gfxqwNY>1!xXw;AC1K|{@DSxK=` z5n&2=NO+_nI@Xq$mQ_$%Ro~Ll-8VEkF*Os+6o!hWGPzQz3JWKeq0<|%M54u1S_ZnD3*vM4(>Ax0M_;nIRf+beUD%;6$N z$laFG%!Fu-Je1F2qut0<8l5Q)mXKdkS=)%#FgWZ-3@KC=}Ks`(;7l7@Gqt5W@Qp=cpe8Ae=4u|y_UMHtMmfLMzewr|jDHE81;S9NQlmG;CZO9{R902n z(A*tBrch}(oq?9Y;SviGBv3>Eh>j#B09pR(xw19(Cx;e^aPVOOePlc`Fx>R zrVQ6cnXL&)De119;-;>?fuTEh@7=q9-_PGaARr(xFvw1k02X?ZkifzX%vJ{vkBoq& z8wp#Dgq^@aNWe|-c|4r^4UdN`gC`V+@|lz;59c0CjSUhjDsZJFfQd-?j2F|r4Mpkk z29=QUVyd^XJTD{3W{oj{`>`M^ai*kYWaLyfw6t|}_xAPo4-5_skB*LwPfV^P{8waz zCS0x2>Z1ULNDbjAB_ci#j)2W#v2gY&7K6#=3x!-dX?|vEVsx;lqq(-cFgw)|YXl2n zQRatQ;6$R;A|`2~tF|Zu{2992Di!ft0cD%rk(^sxURhJu(Ae16)ZEhA*52OH*|n1J zTDJ?B6D=)98tg5Q2uCY_( zfJQuta8zozKiWu*N{0LyECDV7o5`T#)SsU|emFlj`{2Rs+{4EUj~_p$F*$7f`ON6Z z=;-M1U|)AfOG9;8VNS;4g7}P=GkuMvE_-yim`#}-Y^^HDbSA_?C@>nMqX87N(Ug#! zo|%=KUr<uyy>QsS5KKQ5p3h;?aT*mPEzHfp)5pig#wI4GXJ)47 z$vA_KJsj=Bdwsj%=?yjI#d)q2hc!A{gOgagG4rCiKsQlCG0`sDHK_0k~2AZJ6s;QOd{gY$&csf z=O4iHyV_cs8fvRb3v;uxTrN1WoVuH#;*9eh404S6xw@pY6)TGya_Eg69_&)%HzH4tLg-6oS{qt3If#C@U$-&vB(W z?GUNdG9il^G=>rzD*!?bybXy(qD3DLF$D4euy6*2L}L*_8iJrm41p6;4yB4i$p=W>(&D4SLcu-;Ab_}%6Jm`zjY`6yaYE&)@JO9DT%l0w zO%PIIt#P{{rD8F0?BQ@rS*|N1B{4q6NcaksEEGI7o5La253XJB%*jr%nRGgoFD0j@ zWn|*61DP4=smTzbOpA}F1W)w9txPbg!8`-{o61mRiHZ!9i-l}*0ErIif>NZHZGy;z#D|jrah{vLn@87r=M5e%g8I<4<3KOCr zgh3&bQVL*1EgHti1o(i7-p1mTC>ftkr#zjTo*W;6n9y8PT7awqu3h}BNCej|V^i6o z$bae~>W3<{F>zq0c01NTJU%r!+)h6S zZ4c>8YRH4BITFqhh~+ByP2h0g1svGvRDW|tZnD*&f(SCz+f<$l7;3|$ zT(Td)ArMKV;8k#rSPc$Zr`Kb>gQFAUgKd>L2^s-|N(!79?CI(N&rn&Mo1WxwBqpb2 zX6F|c78Ey3Q*kVCd~_-Zj#4NgZWam#mRQ&dN>y_5a?;`=gp^4zkTjbS95x0HvkcC{ zpplE%l>68329W`M7J%j-Lg#~D6iXx`Oijiy8d(dV=odkO@}Ru3B-5ssJgdt|vBg9Y zK~<%Y2-vij&tG6P2Ac;CkjEECSQG75i#0K`q`q@#`U#aKkVTjibE)vETB%482|0$%9&gly8*J%05Jn0Lu{2j+QE^_1 zMZ=#dcEWv9foBO733)6U$$xlo=$?NNiGt%a3Y8_*#h7$jz1iU^t!wX}2qe>(Jds2J z>C>m59zF8O#~&O%bnv|c2QX6L{r=YK!VJ4f0~Zwq*&MJMMlcoHQF*IK1WDAUH; z;X*l`7#wCnVUE+R=1%0>AuuD4&*yPiI62@>S4Zc~JNE;ENu-dVU7sX%CBe+CMMHJVPr1Gr{DlgKz!JqX;7G#l4s`wrGGVF{M}b%u-kaFdvNu>|M;I zO;#G8r3vQyTdE2m_luAV7%vI$o@kD7KzRh$pe#2vC8x5jrwsrDFcm^wmlvL+-tAeD;LnH`Y3 z7uVei;cJs?{JDm#PI72eYI)ZKj3tyoauBXo$wN7mu`>O#evV-wR{xw(+g*ppISdEiLE?(|TJhiQ+0b>@=K z-or;Xx_j;Pbz8A&H9)g=?b>zg)~zKFS`PRuU9x27&YgE|bv0EMWH>B3I3a2)D`VmD52WoX3{284RMtz+?hfkJ}m_&i%OAn2- zCOPfVa;z>X!3j(48V8pJ)(752EK{gda&f52lH2|~)Lb;i)+CkpKV^y421`N`gnvA1 zpDQyXEyd|bh_jd=U4R%XVB-rN_7|~~ftH$*oYVxfRt5RllZo*K3}>QB9o&XUs)FJg z%sOg!AOqZ$B|4mY-xaCWn(Ub+js5;16b2hob0%`B$iW5r-MzhY_r61`*KGn^mb$H2 zxe9P$uBETrxfbC9cC&2h(k1V{%luWA`W`O;ogA!Mx>=hRVT3XIHe{fk)$zk)w#fX40sfFh|8CN1r{wP}@s;pD_Qu z^Q`RbY%sA*lz1e>Ey5DYXVD(D#XpJ)Xe`T1cf>;J#$%BE2b!Duz&j(_hDO1tDBcmg z0f)>13I)d)6#Xwo!zbgfiuhd_^^bXq2%SdCcr^12=gA{u5;F@b>RP*dyMYQB>?pRX z485kwu+*CVhcqrExKS}+DG>Kl>1hYhZ6Pd%07b2oK@?`vUd|7esOyEpEwyC@E@wO{ z^?6K=SOP>ABo2shu{%@KGhNv^IbKFrFNG(OOW46Z?L&c7E`%_9T5fSwLsLUzb6Z<| zo>jrZ!A!P#c=^1$beY@o6|2_ZYlUleu3n8C8NvhLc*o1rW1GhhKQ&gD9Y>UyWr2-Zun9N{t zLuIHI(?C%k4e3g}&6Yp|gP@U=WGa|R43!RM8$}q1cSiLbKO#0UIVr)OmS5dAJdZJW zBB?SQXhD6HPT0&#m6-AFG(piLHvDxMupa1F;&F$e=&{AeLndSaItmJraFqg5a>xOx zFCI+~cU8vDM~uV46lA4BU8N0EL>P?_U^26F3kplhDy!=nnot$J%b39S>RfW%4^O;)d3g|B3-+O={egq2_L z_@|fWHuo)Cww%9QRZ)`fT70l9R0-)UgefAQ&xeOsRMphh)qAUL^(2AX5FMq}#Uy2y zHxKxea5i6rsK79_irb|dwpq!o$$VnxkVZoO2|K69G2-_j)`!@2M7T;0IW!BWJe!{w zY%eoUDLadEGf^oCHKULx2@8vehFpi>Fo<#sisdpINkA50zax`LKg>!@8URP}bf zg0q~q0$;uZ`0QmUguIJv!+qtL3CF@y(<86nDYG5%xHf{V^x8b65X4Znd-F1LBXn48?AAUbX4_1XohOLrLDcQ zySJA^mSTTIXoQ3iL>mASP32Nv!}NeN|jB!p^VP!*m-`Io4&wob2kA~IW z`t-vuS_b+$o2yD6P4oGUFHo_G7+b`h%H?ugu@CWbkjw&a&SKyc@{{?Q@xh+92H=3w zEiC6uXMtZ(h;>IQatd>Y7_r_Wyk{pguq)HF(BijZZro92w3Cb__a%0V*dHdHh5-^|h<>dA0@O^m z^<{wLjz_6Zw9}PaTu}|Tx}mwFw|{74Y+?%HIZhTs$ao#Y01j5j>m+0yda3}xh#=E2 zg6I;+A|cZ&tZL~UeeeVWGK&utmK^GSr5xjZaq#p49mnRIP2G4+s30$4=*iCz?|{Aq zLKzrkpp@YQ@CHy2(N{$L4a{4+EXf;+q;3I>hB*M zoq7BWVS#>;2TP~S@Xr3gJHrb2n2_>62nw(Z zWF5i5K_O%er>LnCKtx4m5{UdQtEi@JVDd4Bqs&UKf;v|pWi;Z^E0j4w?;j`edCZ4- z81scHk)zSELTg5*9?U@p>iLVGF~E8+w7p(Dd-8aGc6xkdpa+qW<;D3~X}~!~>y#8n zZzAH?5^Slp9bLWsKv#D43{Cxl29vcbEw{X}Yvch+s^B8P(}~>6V2Cm(Yx26(U+vqy zT~s|hIRp0muPJNEicN=Oa*EvDpF34YcOh2 zSr`>ph$3ZkV{L7HOAqiC2p6~*OiW7o6-H)|euqE_1Y$mzgz+dGYB<26dsH*IyN&LSZ}r%+ zWizspb!)*$7W1vW2gOR_VkMN+)WKD)udc3b>V_kMgGILDJ2VwUTtHOo!r`HgCOk35 zY&KAt>@K@p!l?~rggW%eCqFrQI3f3;s|AA30I}bRN&3`Y#1M~;z;3%c5G7nuQjn98 z?11DKxI8g;%21$`A(AsTudJf7x(2GdvYM7|v>4DAVJ zC^omWp=LuzXD7IZ_V%`x=EjEF>Pp1WW~BofuZkwf&Z#|NE(LHzT3bGZ*Q%Q8^76{M zww{3@6xZh-J=wuQcY%2RA~6YVNM!sbqs&1L)fywYE(hylzniyic&uOk&Rg#;-@IzW z#*LdcZ4zyM9X@W{h;P`s9)%F7NETDBCGYO^r9--?2#aw+bE3GkxS*h@tgZ#ZdjB9a znrHrq90HL$MFAW!xMd>%g8D>KrzowUG|w_VReC+EcChW#(NB+U_YZ5al|kc;$PC-t z86CduZLP3h#D|v^Lk^Kiu&_WwBI1&Xb;#^o4)k^nDXy&S+`L?uD;x4@`0=(*)ZYzT z1mrh>M-U!}2mR(3JTydD|8IiMgUSH1**gFOVuT@&)u(>i5;OntiXwqb7QZlrd z7ZYxHs_!e;Ojw2)fPf97N2;WwWkQBCH=?R0sjI1Pr1#usM?d}KgD=lAOQKxBf)t|; zPdTH)7wxyWr~nd{%vUKyv>sK(fCi2&OqAl`B%tHuMmW?sC=Z}Vlahg0e#rN#YF-)0 zuK^EwAT#LKU>)(`3Bw6A_Cm7{y5hurj0VktM!lH7axmlQ&`IAlYj&Ocn8Y-fE<^#f z0nL-L9(tYzl6vU=z_~)Hz84*EMmi96uV!|CCU)alTLXH+4S zW)Pj=2vDyi33`jrSSu>|H6VeTw}9Y)xCAJBhJ=mH3{l4gUX%q(<4bNjVw4mx7GL>;o2R*pez<7L?uF$hLHYl_kyUh?fV-LnAkF+O{fL#7jm*KX5D#%MC|N3ED8z z?EXCjfZ?Xg;3IO^x*B6){V0u`d{cdn8||T`jbTXvB#d zcn?zWJcSv6hj5K2?1!u-0ab|caUd5U0EJQs+8r!N3ZFfk9_eduEHRWm>F9ezV?wJR z5=cm)pa}z6JvhjiSRd#$BJl}oQ%Dy#7BW9I(%+Hc_>#eJkVYjv;kR=2{QbMV)~{T? zV$Is4o8MixY$X(h^wsoLKC9QRTZ1A8q8y1d_#J=*%0o!LHlHo2X&xs*7)N;$2U;J5 z!h^7ly^y!SLs1(U`iQ8SB?UnO+ufR$dNW0qMtz{1aq<`HQ$G9X{e8Q4?>l%%YN6%I zP3XR(f|#D*OH{>HaPKk3SGQQSs8?B-ofzzHtt*Pm=`XJA@ki+u)PA6L03rxUexTQ@ zk^lyOBooj;suhId2s`LEfq;&g!p2?${zOiIvmY)EI2r^vgZg}KyuZDnqA)uxF_zj= zK~Kx4K5`{Y+}5WwPCb4?}4e+hXcMB$h~-BixD94v2N(GHK*TQ-fViph8MZj5h|Sac-p- zVxwAVuedv1!=?s!ZdkkF)X8I?cr4qnbouh->=mq)-fPycTfK6T(n2H(krP7#hLYm- zU)odhs}aN94Y|l-Y(=RJ5LWx3!XqvsxYm%lvA(uCM0+Gy^ntmi;Oru7+^C+_&Q&CY zNks?t>^;OLT@T8Z6o*1r67n*G!N7^~ft&zZXlT1U4il%6;X04@cY!#gI5&~4cEzRU zmqKG53KFP@+B$CBynW}MUqDa@kHW`(MGC=5uLmPg7!XSEKx8pd@Qv5VA`+Xx1XI9? zz~Y`v4|UW7hYl?uWhj?HnTnuMT?PEemVp-kA5$1nocheN-o8?caIRVQ%u>Tgr3=zP zLGv2c3K9m`=PO7UsCiFd($QQ81qvW+n54v8otfZMp?M4v093rU1Mv^hb{E`OE|W@{ z8|!H%s9RtNd2}qOUvlG?Ba0zO$|(7QdscJ zP-lHvPD(sdAF-&xejTD)_mZ+0d}n?sT|PdPnd(_@3(ZH+Z3KTb)|4*G}5BMgMkA^Ld^SW9{)((EunCq#t| z?6nX)`1K%^fUyGK0M!a06$#yWDp|Qm*@MN*j&uS2pJdS~1dQjCJ@v&vG^=uOTk;D* zGahd)%}Zv5Qw^Dp42PE9l$()mHJ7(6HZ!5a3^FLA5iXJpsf^$z(Xbb2k&9I%=qMmd zv00)-S;f!+HkxCJj=U2*H*_s>^5D33(y+&qeJxc)Yb=z7JpkDp5*Y~WfhOBDDd~2E zrnu7do(o7b;ezHHg@wHw#3a$B;; zcel?X-Li-hQN|piL)Qaktf22BxJU@FglOUpg)}$X)lilL9aDvX9y|)2$fOvpk{RP~ zvP}ltZ$(CymKm=GToDyS3h5-j>PYN9+fdrfKyobb(-K8E{6=lK93&<}DbV#e_VO8& z`GY;sL#-^y&rVMUx<$uNPNLw*U7(gDF8yy5hfr;1pn}=3!Ik|1GG$c0K%xwG1!fiS zn?T43)D#(5)hn#}+Nz<`ZUC{;qrrxPFO?f!lT__9F^-)?CZ4-I)_=v=0b=KFs zaBc9h`y{C;?csyjP7EYl43L4nvfidxO})#XKbpl~314&l;A zVuMHqnKPhkXBfWnO9&E)J`?W}}sK zCi#m4=$w!YYK2iir95pkVg9ufGabMJ&~PSCsxd=%%&M33kO)Z#{qb3-=>`aQQb^RB z7L>t>XE=g@VALsQf?(;*MX4J^Mm`7QkeQ@k14{G|dN37|1^OaNN{Rw3N5X&erDoD#TPm2?n$wv|bcaA-!TmqtxgKsSDRe zO}K9}sYUd_-m1*la6aW;b5TmH4%|NzE>T)tiadZdpsiK1*^G7_h!*@x4P3uG8P49f zcB#A9_U${|Hu!vS>dN(N7mn>(x75db*Q$-{KiIR~d)H3)|1ct=&{?)81d-wjl*VZy zBIp`=I2LPA@F@3Ni;@gddcd`k6mz(kgOmO529up-HmN^rP*<1nI3~3RfyKHQt zxxk?p(`UOXvK%o6E#mi8sgW9QnFp63b$y+;w`1OZl(@X z6GAz>BU^n|LJMZcN{1?1o-&}2Hi=q=BSHOwJa^K3`>L# zU7Tjv8do0>Sn`x*#>kwr;SaTKLnBipfg!VL?D6A~ax-^sWN2XI!OTEgT~%c%m=F-R zgbkr82=u>0+6)CEDs;fbze#ZM5YUuzD#j%9NqESA5r6?=j9@$n5(-H1@PJ*WFti_Mp)LD`TljBIVm$To@+^Z~M5 zi%A<(`=Cv7Cy=S+8R+^@so}zb%^TNm^W5=~&*wLVUwnJ+6!(nJ0q6s`d+pihyTj9C z>jnr-&_P3#4w0Ad+~KqPlXN8ht%uGVYL){BSzlXKQHmw#MC4l~4s9rd6yO&SOyf!; zEiqJaRb5JkL1Z@sDhlFa4c3PF4p{(AY*bhfb(|P@LlJLp>gb*0n~DZ8HW%-4@J3sk z8tR*%XgH-D52Dl&8l>>$HX#hpW7O3Yz;Wy0fZ=Zwlq-<%L&s zXQ$iJb0)cXnKjlH)oR&A9kQi2w+fr7nPt`e%&5G6j3eZXI=KV26=gu9fJ_T|7O(o7 zz$ZYK@iU~WV}oreeAK>0B@++l{}$M64z!Z7px)A?D20&2=84tONd-;!$ZURaY;KAl zH;|O*;90L&Kuu|G#r&*vGEOh+qO>;pQ)Kwb?>BDq*#3df@mu1P7p|OFUfO-g15&-s z9y|B!^xU!mN+_@_L@yxA+TjEIwfEug{D6TZ3KyIXiX0GFl0$;A+cl{MF(V|vKZwGS zMI{wpy^rDd4CxMkeh@h+iEsPC7H?3*c2ENx0=7_4Q*}AMjHxR!{+LP1s;VDk>T`R^ z93gu+k=;{SQd9u)XVf@Eh6Rm9!m^;n_HYWcyA3w#-Mb)2UKB<{i4@>}|K6S3uaSi$ zI{6I}IK*H8j0*Ne1+Vx!urDNBMy%;Cu!UiZ1kk;#q!@$FU`fiY8+^u=My-2lWjANhtr<)IrW)C54#Ie9I+$GzV3Gv({1(ai1B1uYHsagsjXu88BXbprL`92 zAfbTM4&3M~+5u`KNM0XLLxIuO+E{E=6R8i-4vd)B5%HqGXVO3#%oA%Y8C3%$uFB-h zFL%;iH-3^7s{J+6{M)Buv|LV1ZM#b&U~^j@D1~u;GA92gy6ckr)@}PQeRPQup78BA z;&VIS_uRY@csh?AJH0joVfr7oMf4kB_UBusgh;+3BI(*UK-mS&K}^QQ0{sIqt}4b= zf1fIfu%_q6QBrT-j;IU|h?E!K`z9e;$g|XU#K6~LZZ4yGYv2<0n-vNMeT!O@{l{W_IO#IVT4@=9+o*<4iv&$(fi^zzPWj4h;C+oM@k zNw`Kz@3MrlpO=J(XEZfc7K5jhX`)k0qw{Q$(Q;#kU14`MzmUXaRd;mcsb(uP0Vtq| zOh|14G!GaTK(#P8HQdt*QLU`3FwG9C6_l-mPXMP-TvSw$pNnz1LZvaKVgM6F*m7$7 z18MAFW1a9sP*{)vyOkJx;hRJ?jcv+IHtY0`G@hwvIOf{DAHOFD;C{PTZ}d8P`^d!r zj$9FP;)nOQZ`lYu-X;$puT4naL3qMlJHfn&T5Ic8&m+tjByUayuE&)HLJU_X5L@^B z{X-a%sIy91la8q?DDycNIJ)yl^Sa_rMURIqNWsF$_ymk zA*o^%j;xmFVzaBNy**bsU6$s6sRbCjG0=zPnSUrZB_fY}Uluk(O9mf3TPzaLMq^y<1-E zwyxZ`>$9tej-UJK#<};uJ><6m7>#J+-T2j7|3m1-EZ04aH_DN1W9LTzT$v4K|D z^B44&K?5NIZBkiJ5JMaRB8<}dIzuEITeCjoX1En1oW@YEN~B;SlQXN06x?9f$EGJs zE`M@h`(OU@*FXK^omHE5d~oU-`NJcJuYZ2P$9*FqVq3FzmD{qtzPoo2&hOvU#n#=2 z&YK#5b{~R1_tXr`Lg4eS%>~um!R4_zP4}sMRa8QHVO3>hI77VugNXZ)aa^Gy#?+v4 zU?f4bGpBKcB?ryB_pHwmEvJwXn16F6d!wS^fk~Ga*lf@Uetnu(8iFMqc&QeHPnp_&l zG3MQ&U;}xvw(}Ri+`Y!l?T>H1`T85bU%7PE`fa;EJaP8>OJ72vMWli&0jfQ_cI<%E z;y-96=rOI|zW*q*dSvp!{Nqg~&>#V5-)olY)v$do7R}OxBT*(VJ zhu*r(AVpB5p?(ZQ-W@7ssKB22)2-`gKl}9f`c^)iHVhsGdVZ+y4CGC;EXbT?+ zMOb@j2SskH?3;W_H~tI~^~CZg(8gB4u%g3Zje_A17+_@3UOocmyBFQ3N>m|28w`5m z8l@CmB1={A;K>Wbkg`yfh(=$;*f>QTomN^8J!_%|OErhw*tf;^tD{#9Ox71SIPn$r z3gJzfxs%QcC^0HOKmCR8+GWfB^w#fQfBp5}{(1AV6{|OR>^$(n2k-6P?!FPV``q>{ zI=%k|2?PwNTfTbJu7ej7hi4x_N=Q)IXl#$i$9jYKx}^N7hPJ-(ATrJ*8-uQYa^!;F z*`Rc#{PaD3d})=KeHDxDWUvFvEK%S5aO3Kg%inu%-}=|TzrE~4F`Xxrgz3z2@u04h z@WJb0!Qk`P-n%1LIS`^FiHD@ae$nzeeUj20^sQq5l^Q z5YdC*Kx7m8BMq!h0%!>q(TZ}`jQtGk9%w_L$d`?kEYNDHVn(DqIj)isYhsVJ$M?62 zYw%F+!^XN{y0LP2{yB}U?hiA^+ESaJKpTMzKL@iuFbe<@$QMuMCI>s3>T0TxJwZ|k zE7huD5(v0qatI~1_u*3*^Q9vu9Y}xZ&tMu`Y5xMwf@w9RU=R=HHScj>=6&$8Kl$^M zA7NrrQ<=UZEhsgRjrZA@q(g^&Rxexn)?07=0nmEm%_Uw-mn~njVXLQ?r~9V$Yk|Mr zyL$&pB#`SPXxmp%p;UgghwsO%hRNB-Auyz|cw=Cu2~)-tbj?xdFu%!!eyXrHtM1@O z?jL@3k1jrc@l%>Oq`p9vWxekVL|}Z~t>sNDo{-G@c+u z2LSz&fQexk^=Yg{f&oN^BB)Tog~(}|C@dnSZFqbdcy%ZupaX+03?1W8{s3hJk~D!Y z1d#BMH{l~<1_3lYm;ug_Nfv=<5vX|Rq+_*XbV&qE9O^YN0;my0CC3>i`TEfKh=OL^ z?6QY;<~l^rY|nUn`cy~Plr+D0eC|1wB`FXX%&`gCoilUupd5nw1^nNm*=gVe`=B)g zx|ke@snGLBoR~}mbOspS=$+fFyrHSNh39CW1feOBVrBhQky;;R09OfvLtxZr*SK#v zcET^<`oFeb2#;cxJ7ZV_%5DlhxJ-Qay4U8FOP2s|eJfUj_Ik}n zhY#-e-35sKf){FoEGkhDs{yUQXa8>B1+o%R2LB#L<#i9bXUgBq@A* zq%)_6rVbCK40n(6GrNXHA3UQlh4GXKquJ^xXzT487zJs{#ONzY3f!=wd>C|wlHLr6 zK}$-)V^E&XjzJn*f8}~ud3(>`7y$PI?in1K01M$o#HD0rf@)hAp-~Q}8TT#Ux&O@9 zx6gZSITR9O#iL}_^}6Z_dzVTp|!JbXlxn*hb$foWUDPnX=%=QV}x2Q z?|14??LTwu>gC{b8&~fOHd!bdev~!-N)Uy7!;F(Z`11V4ixM&zzKplA~DGZ znJjemq0J4_K-$ykp>Cwq&dJQYXUc(h6Ep%?<1-JR!72|)qo5eBsw~QgkBU%pqdQ1T zR~~-<^s%EmH}5)$86pewlY$)gn4-`S^W7h}twTxqB2M5D-}vo+y|rSiho=|5eFwCz zUiGdBMi&{?FBrM)gA@@Mja^62{_v$TEvKNgvJPa3L*p|@;3n2Na!Sg}@?qvstz;RR z>Dv!oKK}iwFT7T4{X9gg&P>qXHvZ^Oq1}xR4tW2>*$WrWU%2Q2#^v$%zyIT(OLp-} z>gwz3P##_is+Am9I`|Yc-a&8~5)lv5F6b%)hgx4zSlBW$M$@B#jlwc`IYMXO@O}S4 z1QXE&MyACKd7W^C2r7V#6ieoUe)yFZ(vngD0?vkttOStbkMug3iFs91Pt$8Hsfk>= zk}9(4x{5-hVq--0S&tp9aC3$xAHKkOiXtA29VwpC9WcUMj3gboSwMM#)*oi+qYPTq z-6gu;5Y1*syBg6rKgf5Zon)=W216zJB@o&XUWcNSa!TQ@lw?>Vm7)C|x?L+bd>kbI z@q?xAC%(JSyX!CD6vhSf#QbaIQzzF!*LNwjMF>au27>A@e_OY8E9kv;AiWndrA6oW zZ#~6wxBUnwFt+WUUi&})>dsY#!DO{N({l>TYnq@tjT!8&qKfLO!qgazOwipN@!syk z7emc=Kl$hSqv!81f4Xq(j$Ly-n2KE{pZ7m@=KT3{XU|=@1SUoJ!G9h)Y057IIZJjH z8g)o=Bmh17zYGq@x)Dy`O{z;_COsjeqN<4^3)iC8CZuG7dKF%%!AD633rST0q#(kc z*r?gy%5!n1hiosJ`*l5{=$LTs z{CreH|6qUL;MnX_8b_S)QW+jP;w%VFOiN9JL6pP<(9RN~3?xukq%M%SWfXLiP1P`m ziUx*NQd#D6E{tu#2!YL+kqZ*v_oMA;+4)6+XJf*?LW@VxxTHQ>Yk4bxk{f8&kczV$y#mTdq97=5Gn2Jm$do)&XIg2zSf&UX($ zM9>%RTelzj_&9$U#z4f-CXBW^Qe64Pm?1tHs?@ynxQI}8kWLh`fB#2c{`A8S%T}Ca z->u2G_Ty;*E#-P}$j!5A%12+HJ9qZmGvA)O;OXVSi7S*stJWN!nCc=>Vv-n$ekX7O zps2wdu%Qp5#olYQ@YK6|LQ-l-vaVR9TcJ_34PdP1rHSDSrBl z6DfW8kRMmu(?8JP+Yjy!H0!!v0jJZdF%L(A5G0O335i!=MjFX0m>_$33SHuXPN)f? z%t(f}F+gq$y;FW>=g7N7&!HuQ**& z9h!dW!twpWmX>SxE-BStp8WdTvw+h#-+X%>T%R|%KCoV2jl@E{9c59t1AB)m6X-Rt zBgirqU;6-W+lYtZC8>ZXmdP~E;_AkZ+xMYJK=aBPF6GydlQ2+yg@8ij5=z>6fD~%& ztH|Lb<`wQ2K_4tHjKO;Tj&rYRRcdev%PyFh4N}a|>dqAaXMB7=nqE+VCon`1kjL zsDAt*L@8zNM3n(X;j&qVXy}BXISJIrC*RsZu&)D*t_Zd968+< zd4AicAv8(w)iVLde>igb%$aWiq_0n(IlIksySFDa%+_yoKXXlqURnV!>X5thm9vWu&mZ^D2#8)SQ^3I=`#`xsy;@bA%>5b@T$O}`wwRuT$E&J@JR}HF5!Iv&5g?3GWD(JM$KgAJx z?ZXp$53E{^ulfss^bQf9-dY6dfBw3DITI$0JiyM-R56h?5Uy@9`9tAq?Y}-a{IAcC zf9aQeBbXS{u%+ab*0*Cgym=yccBH*LGhU<$IkN9!RM^>XckK*f#j8?-KlBpba>Vm5 z`ySkP`P0+id~*gcI(6#w8L%ZU_jPO5uHU$6kC*~)(GbClK+qwATnX(Q*-$NLEYNV1 zNeAQj-~zGf-sU=Z<4{W6x7PyE#7<;%d`b>T&k+O2L_J4DRUniYkV5bQBq3QvVu$b- zJB=@q7^Nb|X^9ZE77WQ89WUEdt;umM>J(#XwQxS!SXXVAa^|1J)*}USS7&QeYyZ>} zDhLIb93enta6K2n>xY1wrBYr#dptigf#_MF{b0%B06Ic;SX#!LD>Be10mlGR=>3^PW>|*Vq*26EtB(`RZ>x zKRvW;1^qt|`or&k_xnG*^{4;&^OEh%72eC2zXvnl`xm_&(W!z87)%HNTDSJdzdk&) z&-c9}pC0)#KyFN}9teWhG{7`XJT^1h4b$?8rf?B8&n(z^h*^2 zt}HK0etfmhrw70M`t<43-<&>m^5m&+&TiYbdF`sz2%~MMOX$RS1zfUc3$S&9M*<=Z zk$$n!Q1**J0TwbhHPlp?o}8QzExrBSy--`}_4_abE|7I%lJ1&!9h^!QiBD6 z4V$bcg@&*eG3srp$aWZ2eCiA|=cmK%?a%tu&FK~w%^-+hXsLK)s%%JLy}*rSEuffg zZEtI8Xzm%GfBJa#Av{K+)Mzb@R2Jx9pFWzKo`82Pbhb9uRum(40uf1|^Jx-TwAw-)&E0)mXeY6>ex z64s*5$!G3-Z{_l3E5MrpVFY#rhVX1()-8pWuN#60jL3J-9+cI+N*M@G zw{GnxA0IyS-oD*F-g}Q6J9_N8R3%sFoaJ49AvBf%t1rv4M=7}EyY(r4mv49<32$;; zI&t8*)L(w@!q=bvlrPW7qIzs~+x6L3r%s*v`s-6Czxd+h*WY~o+2I2)y18z{rp>E2TU4z*8!^a;WZ93Al$1e$&A&a!Bpmr(_fytct12Q|A!lQ{X^K; z>qMQ4up+2G98!qaf~6sB$o0ZW3J6ey!^?qJ7>H=ot%VMWt&6QQ=JZwbS_+KnYD!zA zfG6OFWfp4}UPR{AHbJ8s{;R90Z|&}Gs;p}2pM3ZdB`xO8r!St)jSchy+f`o;V+g2e z4H65qN*)tn8t$kq&U9FfDiN1O2^fob!KXj!Z>nwRo5kp?!)Z3Xj1|;V>eSQK_LHZ- zR~Or_2pAMj?){{j=f1laEQm9Gy?4Vu-d(zUT2o+wQ&p_{rnOoAKvP$3&o$FK;>#~jp8N`M zI)41b7oQ#8?Xz?5d+&d=X6?GoCzFTZwL}BGUFf|)=xs5GKsOl>-HBed0rJW^c$0+9 zYE?ghD$x%|gVz81Q##N-oQigh6(x}2@gFZ7~E3srm$@2OeVAKQ(xys@!cx_rZ_wi6) zFO>7({CMI7S<~P^cMGf#Ue)u%fze~5ANNltKbXLl1yQI%2aRn z(A3061)nlnn-MLg-L>aOf^P|kQ4f%`fhnf0Ul z+e>#J{rtoU{EN>%KMFLJug7N}e)z9rC%!tf&C|>0o8Xeg$&6Rqhj#@57X^_R-l#G) z*p6O&0WTo|aWV7pLmFj%Gx}Ux5A8NMKKo0Y3Xe2U8dT4jhL)0We$ZrwZ z2mhfwod5}&Y|62Qah?rT%bWvjtbLd-q|ijq?3$!xdnryW(8LzxcBzGJ6_5!yO>$a? zQ~sjC9wC=V!wdbsA( z?z+;hyz%9G`+oZIeggH%!40eazWnI1W5AHVsOejVmOzdU{M>fK-(jN+da z>+%cW{Y8u8DJF0t@=(}6IdG!Ctp-%Dv8ctsWl*2>6czSNOpW`0_{H&0KRUE;m$&El zw}W597{K2FZw~nbI5=hq6`-L)>Ocz?2K_2HL_rb)das3WNVG-7N>Ysye5SLy$T^oA z9!;q)>3qS2RBt@Hu%HNwj(C_;+%guCC$C9~63*mDU=^C_oLG}3(N)qo^nxD|mzG;l zp0$wli%yAnKa&tV^D|Jm!>e2XF7!GqF1|2Wn`_fX#$}e22k>whx_>d*Spg!z&<|40 zQrexW468v_VrJ7%GjVOg=>T(htCB_I{REU>#LY8D-~Qd3Z~t@2viZsDx#u+ z8JX6aarq|9o-^^|{{r&Jb31|7ViLaYQ^+6?TssM{cJ8=(<-+ODkN)Ur8yY5E`}*S9 zuPs^`hkW;^OIVDGe)m#BIG^UHli{?lXe8=2udmhevb&!g{Ol{4v`cv0d+XcpEC>7C zd-LL{&xC$i3MvU+Qxq1RTzvIz5QR?X>&D`eU^o}FKWKmj4LVUn=EnPo{R3Peo@S7z zI;-_RQXMw`nE3rkXZ$ExbWp0&iL&Rc5oGwM@b z!&+;%SCzG$iWGt8}L)mG>6I9eWBZdIc4{n@?QwJ=EN^t-@ zcHG_d>CWo1GJ&A|4vWoX<%^@zL)U+wxuLdtJo5BS*w956LH7~rUXr#WATpzPkm$S@ zgKiRlW5#fBCq?n`>D86liOKo38^`bS?%k{5GizS=2#?KI(C?mKp_R85yu5TXIb;3$ z*&HVA&nLIfjh82L!HBNy8e2U7h@Dl|(B9G9J$+X|2v(b_`6qYKo;W@^&{1DToX%J; zuK~2G@+wI2r%y*jhxrGNK50ZRv$h<~K1=|XMq8^&OKUpD8b%_fx_yncJ*JD(#wYl8 zMY)yXh4slb!8ZT-=#yW~ZCt!pkB_z&GJ^eKL4v-Pu(Hj`J2)~XCL-8SbML#}->#iT zhI%@h8pr~XU`RtuPGK%!et7OoWj(qdwFG?{1-d^*g?WKwEq4o-fvOT-BdB`&` zJUT8eCb8vwMyAL<6Pvtn*e?V3-st{L`O`5V?Pj~76WQ$8X&`EOF$0KLr(u*Y~p4?yfim6ckxNFXLWq7k98aKWmUBh{_7m+4lXna=dgQY|}E+xfFlj zfKUpRosw79K(8ETC`g%I#mTmSfWMriL-R;bc42&(E{bLd`g(ck~ zF%hBR8R`BJVBkfDhX(q3Q4^VTCQnfBU#Gr(4%?=)D+{EN3^&U>K2rIIr$*Z9YMOc`SFXHD$!0yg zwsB_h*wQI_W&&p4E$tmp_cc>1YTL$qBRRR{wCJG1Y*&Y1`|zM9MrPz_Lq%HdLZp47 zmxqR*(5A-F3Z-ll3h^!43QFz{j?OObo?bqF0Y1)l4sO0-RBlF5eV@C3I3)`9Mrl(E zl!xdGoLwd6Jg~|s6=o*0X_RwQ9o5B!<&Ax_r>{|BDOcCmp}@ZPjAn31Qc8wQ+5j2@ z(|Qg`LP$Jh#r#7Q6r*wg*WW;9$^dUL9!Ut^vv)V_!oB#k;?{}vt91?8$Dh24=bpO7 z<bBzIy8Fkg)aolT*yWIx^l!%_*sZ+_WHxMy&ZNng?c2-=gvIFw-r_&f`^Hx_TZQ zGa{;kEhq`#w5PW(EDUw^j4ocd`+~|$6QVxHH8lYj={t$~8Zu#_zSgGJZo%}l!fJC1Gb3GIY)~l7=Nd;> ztI{kTT)hKA2x!#IQX4NyQeoQw&CZw-YAIoq-V{oyKMtqt$qHslg1!2F3txLnGgC7w zJ4a_X4>x-&TW9akST?_~uFuUcG!g+eC?ZO@3enXu_`J0>RH2SR8x+Qq^DX7&b)DlU zFTr#wE+*{ca9c}H-`M)?r-{b<4jz^`DjJW{V>w7e{)WbSYRr34x#VI+^MMmHCV}Ae zu7S%=FYTB;|D2OB)Kf6|mX}q?shmjV74)vLb6>UOcAf67yH*H(2S^#&&@dHdGG9`O z@kV3s^vd1?7?!S0TC*yru0_Rd+bt$Y{ zMpiD~smL5VbB`FWmDe?C2FIq9bj)6giK|YubqbNwb+n{4(`gy4OLYF-vRDcu#6R23 z*22`()XK@-)5p)-x!2LdH<&`>g0tJ@?B(a@?cw6!W2LCZP(8E>59E>tt!>8pF-5>zkr`AcmXUxp?^phfy88qf<(|jt7VG-He@G4yw7?(<;Kk zlIrGSlP~85TIwmPa=h#2aICuufu+s}_d*^&ZA|k><0s~7=b%=Zl zHoa20F!{W`4!^nHkrNm0M#aWPKZ^?S?5h;!mb4AbZrphstE8rDviHEDBS)oWP=TS> zj&uXFDw5XYm5NTGycd}$0xSi&23j42f02k3pYSh4Nk?VJ?8O({tdiozrO9*LoPsLh zDR%3to7pD|y7JHCRlD5RR*(tF04D+3u&m6aHxFJile0nC8ehKrikqG*ASP=0`GT%? zGONPH;?Q3{xOw3W+9B&NlQZ}!oVWKbt)02D(%anFId$d+CHnDG-g07o!|=*2YC=Yy z2EU|p4ut9KtnT6%Z)qhXch@pX`K6VVxtx~lg^(=cq=*n#I}1}|BU5X8L?ws1vNjj* zz=$|@s-UvN$-~>*)78=5N$n^@=`it$R8UYrV~0xEOPoAQdk1@~s$6cQHxAz1&L^dH z#MNzb%0I{6D=a>((9G7^gBq7y+Px4$<0Y2Fgn1nl*Y))&4$7GyXOCypr%(9AE4VpD zcsW?&=Np-rp}}D$$SSvU4~$}_3Tt|f`GL9~8Wi9c%S$JQuITBcz@Ych%}Z-baNV8X zh=!~n=HBJA>nlUGMft@I(^sERsjpu}`5l&&Q_(isbKsCT2>j4#BNj5_J|%tK&4&z$ zB3iu3l=#R55qwFwYlQcSxJmvAw5>MbJAMJ+l{WQtTzJFfW(sqPil5fp%UFCkS~%R! zF1c`_f>BkFLBwJ#4skGNKfC*!LF_F%MiwtV=fLL|Wc;L*^*hDLYSTe=eDmb?<#X$6 zXV%VbT&45jKYDX{a&%;3VSf4Cl_#u}jI0!TEVE(l&YMKgOi$i?N$ojyDHb-F%{9|A zj#>&zI(9C#{0(mZMt5#c19Sv#D(c9}Eli9I3{5PotZgg`1@#UdexcEL(8}ALJ-s~L zoE_}!LLB8#zoF-=#2~ytu!x}I<{R7Dm}oOY-0du_?A$}*`Ssv(aAN&Ez2mY=8tgqo zL36LBmUkWxq4M%73Y#l!HI7JVxq0NpFT~azZ^~$|jtGzO*3k>}bhN}DK;OX7$jDef zJ+;`{)h{AGIj^c~)*oi*A%XsWfqiFl!RZ2cwC8uPoISO&bmGL)^64;IT)6+j1Pm7@ zd)oV_R_`VW3k$P2^cYVIb6YV9SwLtW4_eIsV5}ea!Df1otMzwa^cQ#u2$mdCuuQ_( zi@llY|H(o6(>J{|^)R=ywnV_?Wj!6rnR>BW&TZ}GcX85I_>d?jGU%|X2hD^V|LoyQ zMk2qUwrldt0~SBOs3;$WB(J8POZqc!o?;An9`mqEmmvDN_4Li1GjkK8lM8ECAJcj1 z{M?d?GDO=rdehC@moD9YlPIX~rCy6k$S!N{pX@swo8+#cAg^OC%uVdOJeEhVYh@;C z@7gQtMo=@vp&27!nG_Z^+PnG$QQ{M`%B)?yygXbS?QLz`45d+YD?^D1k|5c<0QMWi zQkk>>XB&bTBhwhALZq; zGMgrOS7zT)1kMH1tj`o;eoEZ>HqW=k6B*yDm=iKuc)`bU*oiz77q=0%57SEtQ&( zUE0(;aeT;=ULT_ceLJAC-1p+~7A6Pnt z;C<2Mr4HEwF^m0Tpnjsc7>lRvuQZ>5JcNnZ$fD3AnUsGJrUvTj7&eI3IY>=NmxR7~ z(;B+Q7GUi3W;ZQU z1wE&prQW{Z_d2Jhqo=d6EQ@(%DF3E__Vms*TrYUuKYIG)5xn=KqeHJnv(tqlQ^vZQ z0)A>bj}aXf85MtfbgefPhY&g8l5+sVxS!862VTZD7+<%%~+0I zuXozMRW`uG-pafR%*PyUZEYO`J6UZDJ0}@Ra$vT#E7BAr_aLl4GiBuB#KNkuw5;akyy-)67wrF-H%1cI%tzNwK=;fQ+ z$@Lq{4W&spFGJ5dPl#awO9Cdg5fsxJypa|MTf4+O4Ee41tB|;r7 zExkR5x68OuFd1ZCi|LEto4fdvzJ|Z2qiK+-Ii|_*&aEUHN$SkV#L>|S?PNOEyU-|s z?zR<${snMc&lD6E2D=A)SQjRQyIWUN>mZ;YeorStsSOMj4gD|+O|#_M^yT!>#Ol@( zV`VWd`5<=(Yx9bHZ7p51Lvnve=$e_87nmk#`Y0LfBhZ11FL7v}nlTNQVvk?|cJKb< zXJBBa!Z)!RHk4hRt*{x*PNLr0pye0Bp*ydrz~ZR1_xN_cOxaZG3x&t6k$4>EH*25VbtxHlHZy9c}S zu9h$pN^?7D&n_4wWBe=oW0k;UpJ#H1khoM#N;qS$^^vMlsuWSX-GI8SMRGhq$6%Xmx#6 zMMd4E67G|TY~;l+CV zt|TvqUZ$U$ou!TBZwI86HH@9@tSrndC{Y&nt`bT{hh$YWw6rwUkIEY8WC?QFu^{R) z6CfjM?&#@-3p5-#V5304xirw&az6{^s>L+2C~9llhkH+TO<0 z6TI^eXaBr#{r0_kw{G1zKRr$xe49jHZ+fF6370AfV=fQ&>5F&yqx6c2GZ!z6%rx(o&f)PMcoI*8A+!Pe0xI< z$M9acI=Y1G2N`JQtG>4 zGcvunn!&R)E6HR>dfU}drx!r4y#myjPBunzei>e#8u|gbHHGnAVI9*s{<5;TRPw62 zXp`-d)Y`d2M>ku?zzT#McN;ZLRTVY81B$yPl{9s9l=Q8Qja%Atd1O^pL3Q^4HgxoK zcD0n}B*tDnG1OF^U(m^_AiIG^W{!s%?A>FeqbetHz~aBCaY#~vM=dFeP~6DhMzK-0 zvC!>eZt+IOV#$5s5 z(b3MbN|;Oyqn=njd;Y@4>Ek_-_B1KUkgC`~b+w9+#LSF{{Ndzuw>^g>W#klo-Hmct zT18h|#isMd`K8&uDt4%jv@)qZlr>BwbPvlbNah|H0f$pYCN*nA#-hDMt$)3$zvESFHh#AwO-F?InnoWD)-p=+KFbto!tE4(xswnd4p%3 zC1Y&>_LVhM*LzZxs&H_kUIj3_TVD*^3|&sPao^8%?S!*&@>}!(`q@v z4WsOW;*1MCp3p$yr=LCryX`+eVIukqBo6y`$o}%1p1y|Ue}32r>&=5?Omk?vxQ@P} z;UPpvZyz)b0|JABLxKW)(NU?6bg{K@@IduuWa$`Gb?!Q-05={y%;CGbyE;2qn-%BC zte#q37;Da>d+A#E+lqOX&;zw}(#oB~Qh6uiX}O*rayoktOR4S_J0c~kpscLy#<<3y zzr1~nFD0$2&CpTM)zvfFE1{$!=d8=;XBQDGq1xK|j^6hAGPJqg9P4OoXz3YW+Ca^j zJ8%lM?dgr{*JI4c1oyzcJx00-qEN_)`@1L=VZelg^rRs&XGA5Cw1mvwk^n0`mX9Kw zN5Esl*g*u|%`0b@XGUgpPXqqOw&ZIY+f&RxEC9il;4G$5Sbxp85HI^5UWSW;M21UYv$p~A9R@ps0D=FUCD z`UT{C&3B)V!5glwzNwKng@+nb9t?VStjrBI6r{4KF+Q=8!OBj_^wN~H^{zN!K>=^L zFiuFPDkyIM^}B=H*}wez*F*cZ|MuH2-+lJ!)-S&N>MQow(%*dj`KMp)1Z4ieD>x`H zFeuQ^+tc0EsWi|%I6lkN-op<##?@ZNlCNvmu3t$>_we@eaC33AwW1EU7juK`4Ahlm zZM=;47%>7pjQt1G_3UE!td4BXRBeN;m%Q#_t$l}&%E+Uz*UoNFE-cEXSmK(4A!1@; zY6`xHxv3%MdMFu7aX6rAmsf#Bi|I|$^U?0{lh5NTKL%(K5h!7xVFqsR#pD7mR&*MHi!0Kr$YC8zXL~z525Zk>SZCdR#=Us@ zYFp~ajn&SoM@{^+ODks{=1%Y~2>aKbankdO%4$Yqv`l0A)-S@r{_0icwXd&VyL|TK z;&8%Le_wNH0pTnYe`+p+^K<+QdU9@AT}x|YW!V@reyWMcWAn1869r_c6S2Ib3e(wf zL|`2qoJp~hNtDPoZRE^%=CFF$<#2^gkZ zw{HFHv(G;N{EII>|JR3G{{5?0Kwxl4aB!f%uaBRvmxsHXo11;Pse?~gY~>OqiW-;k*H<@G@xjUdBg2iV#tb5~v>_un9S<**099<>x+QWEJ_@Uu&rx(9~`$Qr@& zwPfaaC3F&LOzOR<5`2n;lrE5t2oEQ|GBL4{W=ST&p>gR+P1HLz=&}_QU!f00n)0{_ zYFh4goGg@XsC+dw|Bya-^vi#L0zxoz>sP>xK(=Me$8sJ)!9hU)3@mwu_wPQwegQ~X z6I0W%W-~mX{~R#$kWrN1OgI!2j)ZEP8oB!#Mh2bZSsK~K z=A5NpFYxbbYqxO;$Gw`ck~US*IqT=;36l!A|I6 zWAh_%)kudo9e(@b(z1%W-ifik7OeM4Nl3~!wwQ+9*>HDV#fZsK5vfl?;z**1984E^ zNJsq_FfJZ3I_Zc+7yU1|c9C1v+dl|LjM|RuMPB;jg^L+w$@gzg&^KPrWo7dop1aH} znMgl=^A6*-5v-JN-F;jAGPk0ktq0D$uz(uq@9nHfT%VrdJx33j>?|dzB9vmo8OGK*>;|?VswO|d^Vs!9 z4==}()D%r!CyPXk?_l3aEKUSvR?bI`B7sv*-=|WphwWZUB}oi^oQWnSzkW@Guc#`!J3KTzHw3WwlKxa)%8y zqN4cjt_FHWy=Obv3DlDV{f=&-hJJ7xBqC&n9=jx>*&hp6V2=;ud1CO%=S9vmqq8ZmIeHeZoT9$9SH z*eu#}mdfKWc#DZ?^xh2Y=S@z|NV(hs%^QKPh8juyJBi+z6PpuA6WZA)r_8WID+SE< z1WKE|gGF9)vZjumNm2!Iq9)R&qM{;03%5<+fSj5J0nS@VMlCGa)yvD*KPWUjil`}} zmd3hVc4PohvoME*=7M4*gsY6=Uby4;X z1_minL6gxjA+vpbj&4DEzF-kS63r%N`SEHVm0n~59w{=RVQ6XV_fAbsj*pFw2ieMk<}AW4k-BvgH4${8oH3da`H#&2 ziGfX23K%}`{lB>Fgj?h=e3M};GM7iq8okY4X|GwlbLGm|D^__+dNSkAxeL^yp`_(k z47gG0qwmj3X}-b6ic0L;gGnWfA2S~gwY4t~r6M8}m%)dUY-@qxcujde(hxMi=u~Ra z^NdW+1T@qFtTQWn+6|(h2sdgp+FnGQosh(JF`)3`^|?{h#_j@P)~QftfVHhz4lhYV z+tMJh*1`f=nVG2x#$84xwm#BwIBOiXs)jcD@Lq~CDo&9u{(+$p(SVFA@~(pmV}vgt zGdDKS)z-7ze?VsVI-J*U+<92h91Y2Y2sbK;j07_^byackK&I0^4J8X6)6B%9(=Cb> zP6?du?QwAR*Yb)XH&R7KRYe7?26dlW?;x_S7SSRG`RM*n2^I|N zPe70dLmpYRrBk4s0t1k#sEZI&kXAPHbc=`~rh^FyNvH^EQ91SGVG}QP4Ru*1*BC=3Wi4%WU0wSu9?dN%Br;^auglKa zTisnw5wNPNsi~5gxuRBJqYr9^kdV-DN-Uj8-{|WeS*cINcH`Q{=9ZRr5!+0d#&~p- z5)B4XP(UzK&&b$f;lia$7cYl~bHx=2JAz=Aga-|h76R6mzradE2a8;;bWz60xm%9Cp%^+RSP*u|~Qs-LP+1c6K zK^{bInuVpEN0|Iycp2$<%1bNjTe-&*7X`RDG2+5~olG;Rfu6y914Bb2V^izHQqr=T z=NQa*65Yn!m?A+G5TlB&jhZ?*l2ZF63|4bOH1P+stJ3dpX` z>gvj}(wfN^1Ts9pS$V>YTOBnuO-xQ(H>4jmWddk5R>#`wYwA0WzsCADeopCneq&QZ z@dF3ZiQC=~&Jl(`^wE7g$qpGVGsfR0#WUMIzax_}V<%v+OFf6rjJxX^S=pFqsi~>! zvDr3ECnEH&sENi#{S^A~WVA^n*9;@ z%p^lenTz$0SkKzh42^Sb)69#+WV~d^XodXiEtQouS06XDHdF{w=(m=JT57?Xn>quc zSB{{dw6gg|ep6dZ$$gB)Op0^r*fq=8ay$z+i(x80(2Agf`1pmVj0EffN=?fsvvqQIa=RH#2@6^~KC2;12!1Nc zxMM7$Si%#=jQJR&GyE+Xpv6!KVi{<7uz(mF%|)*0lHms8D#A2J6dQX=XfxTXrAf3G zk00DU-}d(0soa!P7dx-rel(GP>g3Yu$@$5FGVZ`w8d*D5Qr$up+;u@e-`dhX`-BVF z$exk(D~)Ak<~q^zc;YpCredsI=?g&P3GvSPiR>>b>K;? zA)ypJ_Bz*C4C>SCr*6|?H#e3;X}L>YVvm}nv$e4*Oh#5h?3Fn{*4i_% ziuJJBB~_p_&42Xx%vf7pL&x~3d-18l zs+OK%csk>9Hr6+^Og&CZIbj6ZEjz|zdJ3zPPr>Qq*v!E+*My+hbZk9OV+PupqA=iz)Ur)+RTOXt$ zD|15_jM?cImit>AR#a1$H#sPdYzGgit&OF*x1~ISqlShiQw#J1Z5>_k%YAYkF!Mp0 z8a7p3+0;5JynKG5wWgxFd*<9@Sku+C^^K0Sz{Ci>tF)A~Ttmy{0PwB{Tb#^Q(Z|_@ zjt)O&vit{_i0;}u%(9|5z$^(hHQM|zAtlYK&1-hx*8FfYpT$YZf%RNpQ^xG$V(Q}6 z-k!>m`f*QVCAKrt;)~OFTGaZ z)!NoOeeM}23k#J;rss}h1v9Z_#x|PV?Bao?rrfN|%rt)LtJNza>llQ`gOSeGp6sm5 zR8Dk&vz4h{bWgH>V?B!+;KK0L}QQa>sIw{&Y`WOz2f%FNwdULE~SEp3*L zHrn=JWEq;cCEK`rgFhKOQdU~q*mk3!45E_Z)6WxgDw_MoX66?bX4=bynM5}f9Ue+a z>&0n|_jmibYU=82Vx^V>?|I-v7z6|>(cQ|%yo?R33jYs4hlhsxYBIA+8@q?br=}-5 zlUw*_r|6SID;EkX>n9lJu{`kHYIkx)XC88&_MYL%>FKE{*0dUtEDo>Juyr{v`BYh6 zUY>9?cVO|_Tb`i0V|WGy!t&zm7{pBFaDYfl#zW6a;_~2@QQ0|@bLr+a(z`~KJz!~R z8^B^x{9Q5UH8(TX3Z)EGdRHW;X1SYw|I7Bj5ON4{2?-+(xB+ZGO}z5{{^)Fk%ByJT z>TB3Z9+6d0G}EvT%`kHFR!R&Y^+Ztx!_33is^fH6g%^5v~g!TxC06yExX;P0=^8zTtz9PB-&_`a)$} z5B2=B+qC{E+VHD_$5$?2K0B0N)R@=N(=#wWw}5p`SlNWX;KhuP@doe zg&<2`%K!jDD5#}n8d~b?6X4S+#Da>7$%<>t%*w9GCG5FdUzvx>uD&8Oo)YZq?rd+< zTx(J{7?ROC=IWuPXkhRcL@$w0wY3Qh096khDf>UZ#T48NlPhWQnbbp*EKW9q=UC1wE z!h!-I=9E#EJ~o_sD={sBWOAjmJg9z$??<%cD6PbG_o3`NEE|#kGr9F5%bj zN7pbbH9pkO!?_n0tHuzxnLr86tQ_&P&@>`Laxf3Hw30>H1WGM}7mOe%6c!K@M8W$D zSzs2_fr%vQZGxr2h)-=3U8||V3eMHlr4uuSFP6F8y=#4QJTCX`O8)8mH%r2-qP)TJ z(XnGo%O|j$@FX#9pCVd;n#zj0!OIN#cs3*T@X&+#tG91ozj$VTxV0iDks5Y(;n>BO z$SR-FnDBdn9duq%Mbl9Ji%Tz`Bh|$I#|s#To42s() zA8y(D>DHfr!NoFib%{Y$6oWE_w0m&Jh!1!mu=4Z`V`wOu91@e1l6I*t>gXHj?`|&7 zNr>`wFwu+;_YaH1;}jqXt%9Peo`tQGZ{k31XD(?yVuFLq^}eN*sX9YphoXX<#C~Jw zX7mk;IIhVSdh!bJE0sL>hrGC>wW*DcJf0+710y4bp$?i~IBTeDkm0E7IXQv*czCN7tYKNwUp&R@$zJ2qz7E2>T)7QG%~S3#pfQG>*c101{y&N5s(M4KE)Lx zxgXFXz8z#ozOMpKXeteKY=ykv;_H z@FecrQ{}e`rwa3O$7ZG`k1d^CB3An_X9Hdkw^V|QRNHgnWDPHo$K!R}JALowh2@FP zs+@$_>#MUP1O21tp1yqfhRH)oT?XUw%G!pOUg4ePr>|Z-5jiJb`aU;@?Tm|so25^j zT6jrZiVb_9a?-}m_7lby2TUV>`B_mPr779C3cyHL71s>Ng#`I~dwO^UP~szW4<0xo zDQVMKKiJa(c1En9gK=tXfV-oEvmYqeA!sZrt83|7qTuolhz~1ICVv*PU@JH0eQRqI zHKszi%wbCi(sXroG!=r{3rmh1lKGh!*B=$zsVre*qba9Dpftq6gVjw=8(Kq1*OXP% zbl1{Y(sX zF^I;GOW?)_TJ}`5^*S8=n7su+{q(z~BMmJgI{Ea7L|7v>Ix;lK&&%D--8Yot!!R*F zcvxJIFR01q#RfPUr$qa^IXO9d1X5zicmxpX8W>wRK`w)KLuO%FoUnqxgeWLxV{M|U zfcC1Cn3(|vjXF>cD$7LGc3(vS|N@+%49hezwz|zUc{snw^F%!E{<&RCtiDhpV%TXJDvn2uoSt za=*M+Xq>;DQA)I*o3pD|5QUzgrK^Ef9tJ8#rWRKAo}fWu#u1wl8tlp|t|02={O@dR z2ugC2`;Bpv2uNC58d?_VO_8ZbncEmY+Wc_jz+nwj6I?VvWM~X(hOrSKQdfaDlAMC7 zw!Vg24}0i#d`@9DHsi$HzjF4>={3k%=4ahqZOu%Kjm zpOh2~>sf!Y%G>J%NpEkT$G%ih$Jb8*n2tJFj-~VSN-MbhqT&<$sf2{riShRr2m6Mm zmM=bd%SuAeA{WE0@`kofY-jB6>gpXBnV1+JI>x$xt@G-0Sm)gTUXaIjLX^`=hFLJy z@L;DDie`eH$2o}q~;SQ6SWNii}dAFq>lKu}0HwwBS^rr8WuLb@nK}L zXw7kT!9l0OyeH>ZPpzH5cKgl^(5}2(ZBQSZ5xtGAor4RY@$_^t*3jImuOp*lM6ltA zbjU<aft_9W=i&<4vQ?0skzG{Vk~f}RF@BHVpS*DC!rJWg$tzD8 zN$J_xpHYPU%BAHbNrs#mo2wgIItNAu2ga8kHGwhb*V#xnU36!F4&Y(J zSdN+y>?ZSJ;ug!Tdlb=RAwFL6@@P0h|!l_Ldjhv-j)oe>HQlv2^eb zXJr}TfhMGFGb2*nh&U}~Hjqqu`38iOi+6xFp=>@`$0O~o~}G8?OF zhEBr136Y8t;^z*wBC;WKOQzMAws?M=T%4_RNS_%?##Ho?cd)=)`k&}1L3Ao8yL5E+ ziphSHEFC2Dvg-I@YX-NoS9GD(_jM-{DJ7RIrQR=e~61G@=y6YVt z7}8=aqypu?oOGbKi=Cx~jq@S1LqC4=#pj=Y`L&$6qaQ89#Kzg(-PO?+qbqY`eNk-E zP*+7)-2j6`V_!^}X*7mDi{Y16RQi#fowLib^|iJpQs0X%%a7!> z`Dw2td?5(iNJ8Xt1TTg?JN0p2lvK0~cN;25h>OFNNIXRIryb`uY}T z7@Td+JmF$wW@3ao8uf#^=5BQrNlil|CPd^Yf{@`9MZ>rO|6@m&i>?VMog!4CXqA8c<1WJ_%HyEjxMP?vtmF?_N1Q2X2y(pM(OZ zwWlEs16<6kQwOe-_83Zgcw=J8a&3KWNnU0~rf3WCo0}VF&u?5#Xuvpm46>5PMJe== ztN$zzCfM`IyTS%2{{kp<8a3R$z*XxDpz`y-cPNlsq97*&Ru_hKD#}1b^6-JZyLRo_ ze_+Qq|NfZq;m)tMBh-!T+#y%S^|2;&cME;E4#~?L#V79KALQ$5W~}TN3(I`kKe%?5 zCb3yTc6KH*=E!gnytL4G+bs6g)fK`m?WBPVqSz)bd;Y=&Zp%bZ0-p@A{d?e=vvbb@ zvHibq|B?K&hhn=ugX7p(QAZ2&ae=tZhL8aC^}!F+(n!n7;=?^ak)iq#a8cL6E+YjU z1{h-^SIIUMscf)n|4z8jn}|>%uC6Z-K#rX_bLrND2e+^ja-h38m)ges6Qv5+8)@la zXCyQ%t*@P4MOv_O{>I%0cdwsc!7{%3vXZ)i<7;b^)R4J* zO>sI0yKG)RxqEG6VW@v-cxaX1*3;WJI6k+0X5;m6Tf#r{`8UdvL@x>u`2>j2sBsi8 zE0)~04?f^-`T3LI#7r*vBn933wXc?h+Dh3hGp%G^K*-*HZEVgdU0)e zZoI#}ZD8s8!^ihl+Y8e<7-0gwLl#^oze0WQH~8m~!9?~AIg zo?ZYlIt9YTX;$C1w0m{QBxaE(gw0aMn@*>qRTviH=J)NE4>((X`QTr_%8C$?Ldh>L z>Xzc$7eBan=O6H!+x7F8p8%3W+tos;w3tv|S9^>Dt*y-TRi)jmjC9l#<*@ZaM)t_T z{n9Z$F+_h%q8FXcF{NxX=Q+KDLv5*mu8n?Fk%`Sk&7m$9usz z=V2Ty>+tMoJ;i(55+YIXU<{Os)f$@L4DFTqhC{t10!D>q-f`SaPuk^1};W_)~p z;^6pTC!Bm6o15#)bGZ*zj-SBxw}rW>kscA>q49KHJHY~*kA_CElWpqQ2bUX?2^0+0 zdu)dCCrV@*itaj0rbTc7S{DA8|1nj!Y$BndMf`5LfFUJ@ADz0XkUBm~y33O@OXqGrqcIrqudmDx zcD1*)_Ri%O!;p^9K2iw?6*Fir&(BUx&YnP%?9#?5SdR1$%&gye_UiTXtFxWud~`L1 z+@AhUIE;Z`QkloSccQAkPwGK%I>U(7 z*4j*0$=g&#`Y5rxJ$zU~79@l{yZ7tr2gk7DwvcxX`%B~h$4ZZ_^*09=-Hwso9-%tT#?>LZE8DVbPA*K%TXWMU4N;38IIPYm|-O|RX2`1r|# z3&(mY`KV(R7OTb#!~|KZY2Tfqwfz(lfaC<^M_%ks7$;x#rEy`9ZjpB zw*T~>Pyh81gdujpm0Bzfqd7~}9KM`V=Ll*C~Pt*q)34Z9TrfCz#2 z$y3eH{2ShDhR)~2>W7S$m7rL%Qj`7>Wjlj_@W-JeBEq)VA#w@G4hUzndKw!cUr)lgxHKtN4_$gI|u zW=48Dx+YFtzH#f;wNsOwCCP|EIkcwk?vAD^0r$>qTSZB6NkvmvcUx^yb{b4!O3ON* zm9}?ph9I0jzJCC~VSG(a9j`LDGo;%X9u^uNo~Hf9RyL0Q!~YzRz^9KOvhUy#X?aBj z8D!_X|M*1|hky9`-yh@XfB%oTr5%Vo=6XtAMv6y>S=m9Hn3STLrkeD=-Fp`D~1Rqjf z?8ko@Dw{n!e%p?B;Dft_Hyy_7Stnz_@u#5{{Vem0s%vjq44XWy}J)ci`JI{f#3Gy=aQ1{fEYf0QC@Ta z)g2Q)bh~~Ry~983IcfptV4OY^^T?2p;1G-z2p0-|Se`CU&c3pQuLA&>YIJqsCIAhf zW)NXI`Sin(LN;ON!k}nWPu3LRyYK7;6?<%`a=1CU;R1~Vk`RND+1%UP)7948+}=Aj zx4g1AJ=ERO0G`YV=%r4KwUuWkAbbcJ%}vEgcP8r#Ko}=1>>|($ar_Cy6lds7aZBer z02uxM*aToZ{=irtf^rwfzZrAF(UHaJ2lzYrYX0=aR_2y(vC82OV&l8xx8MHQwO?Ec z8h)|8b5(pa1KhAAR`YhaY`+#KJ;f*_(X*A(O=7|_?kM0_-`y8{XR%2yWc0N~9A3hx1h_h8{x z4~KwU_&N{}%)t4rtZV7+2U`NG7!ka&dT)4QdS+%~sD;U?E@?dehyevwd}?)T8(AY! z+tAiGJ_m22-nIt7*E@N9abc#nwlE_RVLbm$e%jNSdSM15?FV>ZWanh2q8H2IBnxU! zz-AsMQljwF-_IO)pWJ~^7;6Q>gcxcCf&j7Q+Wh_l{QW%jl*NAh>a(p|wru%e%hsRa zNB%o@K>dP+g-4D;%#HB>75aKYo2rNL5 z4+xl1buehiMQuPJFwMvo7QxxPk08+1(ca!QFg7)}2nOePe_O$^jfsi&?o-bYCU_;a zb+y%1<>giNZT(}DqkZj-HPsC*T_ZEcW=C7g1?h<#HYfM=!=<)@j5J2-wlt8;vQtqF z!}TUHf8b$ebu;|(IspO6eprKlLl9DMaYxT5430_wK=kg40Kgsa^YwNJGTi(74_}Hf z`1cQF3D8g5w*B% z^Ugiw^mpyry$4}U9KMqKaRJC$*gbpj32fgye>ZMC1#*TUM&N%yxeX@MO?3Y-WAQkO zN?%0a#ZO`3At1-kjv6D~SCakkvjjzzOFiKl z=klG`96?z*5nw=j+R)xVgp;o>FRO$b{lw&0Hw*?-ld#8}-_TYez+4ZsXAs3e$xL9g z(1A#(T2CylYihxx-bu0_lKJ4<7rnk3E^eMa@N4{Er!R^?IDQW!UCDz7e*YeqVC%pB zgRErR58r+F!%x5N+;?#E4gT(%ufF`^%P&6r1aaG8@3+kZr! zq4c?;l3uW}M-WLzpjg0zLyBY=>coG7fFdoY2!s$Gl$E(kJ24kTPhzi_nx&2Q(Zdo_ zGTZ;yg_Flxl%2cxV)YPtg2_^-U5wpgJAV5aXTR;opY~c~I7vb#<|1K1{;(jx-=p#B z=qe#invt8|K0X7c8dJRhr_YTVjLsFEeuN;u1jpHpv&Z4LwfAF(PUWiUIA3D7}AMI z&Ds0}&`jUZGlt&eRMbJ#fYXQIar5+9L-b(-PjA1#Fbdw_&Ceb$7{Vv{!-Ep!>BJJ- zBX;EAj%{Cm{_R)aeg7Rys=xX6`=5T>wPzQSpC3dA|M};iZ$_SvKKf|uulpE#f8KXc zLPkb>->$t!q-A6x<#oJso&18ylZdu5Ir_MFNB^&*QjqVE1{67YrV>N>57N3tf+?>3 zhn9njiQK{chh=};u@guC#~~12*~6S7s*5ZmMw^Iy{Sg(Ir(wS_K7FvfTU+}k7qDUc z{Fz1KpA0jmt~<9MTpTT+zsjC_otTa-$Jx30*p|@(e={gKbA_eg+SV3f-w7{08|$yK zbFh29N8W>bi|C2t z7s)iO+?9XbCvoJ!4&|SB9r)&(Z@&KOtFQm_{r2B?AnX6(JJ|1k{yCJOpMLyL)<-)( z`-OyOF$rmD>`L9abKhZaZ+~S6o{d)k2>79*)8G8^H$VJOWg(sV>+nhP$xw14)NNv; zc~s(ntRlXDJ*mB>2W8cu^8VxZ-^tdBeRzd;6ANwE6iLih|Gkm;j+M^vnWc6lNzU zCM2b%r*K$l*j7vKK}H4yF>LDdXYqwgY(Z%Sny7Vvg1iEFiAf|PP{>2rN8>~Bh8r{gHtmC{Q{NY+ntosxwJ3e*=PS`FOkDu6 zbIhKd5m6YDgBgNoHNb!v?1^eXJ*ScF0iho07V4OS(QaTUWU&MtxhC?Uub?B}x?}Ib zBgb&mXa_EUV@-el<#jg(E+6YC%DH#`)X&2O9Y?N?=AkW3r`%~u5h+h)c_B_W{qYFv zt!@^8Gb}3QhiEi8!($Z(pO!abEU;rk7d#S=2NaB95o&?~F9m95LV=fI%pq}ql#E&P zs3LFssZ;3EWm%Miz02Q00hvsG_x-6X4Vv`kf2;pI{qJM#T4u6~NG&?AxC=r{GSHczu2DyA^Z)98wqP*k(4+Lg0 z5slSYgH!=6X^{v9l#1|9S#%#9$LA3mFxj+a^X49y2vXFqHsfRj?h%yao-aIk>ioBT zomIKHt;fo9zPfUKuwdKHTCHMaXn1%CvYXq3znuQ&o3odHxLHzEUR6$AR5}TydiGS; zGgyd45;UU;li*+&Z9@I^%LfkP`y?jIU=zrSSY?2)3|@r<^)g*h!i*{JePHEcYZ9Pb z7?azmUG9|->-@=k<|ZH0g-d*z?;$^({JzJo;xu~;D->>^`ak~Y(dx3+QgaWt8;GB#4c zuB16h2_BMFMCS`kb(N>#^@o_8(Q< zKefH(v3_jJ&YZE1BdUf!hJMH1@E?CHI(cKXP~T9HlV1$IYysrWm;re{s%|>1EURs3 z#?&nCv!k`Z%!47qLpD;?7np$NqDmPuHo}AjY%F7-4^CRStlaN|cix%usa4yihIIpF zcN^M1|7~4`=k$4V=y1f08Snn(oyo7iZ5CeInQ3o{<_o^zk3RAX!~MmOxMZl@4VX~W zCQNwTW*jz{Q?C_NE6@}ILE`KTBjK1Zvd-EvQdS;4%Mt~kML~6;>U_b~b^0Zj^NYP* zqh)BdLZN61KQ_&5rf<@UWyq57)bSXo*|t>{83&?6L%jCN>5B0%A@8DKzjCvvU2}DU0HQ)eG|b1CkB{U zUd75f;V8YPXdPRG<*}IefW)`Q^mtAct*;Vt+g}O}}(*m;7 zVnYM`13sPf>Kjw+`!yB9hMBe&45YdK5}(ZKh7|W4s(tXxiomhK3xV#hmeR zF;TreVN!V_8WUK%&y0t`Bu01^T11%Ev*_}2Ht1G5?S>5xt5@Y_y}XK!zEP}!7F&tm zTRB*8a)sje6A>AujhOS|vf=vif3 z&xLbOYc49TjOluZuI$|;3-jx^1142a0;*?)TYgqR`B4So2NvYPrWjZ(2@{DB2G&6X zSd9H+^wR{e;$x`+8PTEH+^-7EFw2auC_mQNqv$#Q;6zsLqSTB?6JK{tiOCdeS>?sN zV%6D#nKq^f3BJC;4svaXoVJ9~91+$Bke_2V#f=Jdh)Bf>UYUmaTLg(B%qaL zOz0Z@DrvS&n8)(9J1RS~TJ{d&5c-leIcYOeL+aaRNL+lQR{Qz8=l3nd64e+vl(nJK z7&=@E*(xpEXUP~(h+$Lig2&6AuqdT>E6K4DZpqGtFH}J56-L7m6StBt=L0LDG309P z;~*nHXoSp&7&k9W#pjKk$UUe!SesM*D5v~RL;jwPr}y90>Mx$Hx!Bo!O>yLyX00wb z*n4f)W3&TsjP!}R@z^~fzoe|98uOU6>A(OP6)qS<;%p^#EDo{I#8L(B2g$yu=%V!| z%%K3XsEDqj1&xavldQu!77CZDgw0Jyw_o~vVMgRsQ;UG@${#jlwO`FIQQz#%wv&7^ z&DY1@%5Pa!7&h`H(6%Y^gGR{650WE9bYlE1+@Ro@PK3!gz_{Xov9>t)95vrDJ+3<0 zeEN#;6{kzqG*|B1axOD_^oMy>8_*bhz;AZewEqPO7)nX(E^1Myjj>=m+A_~{TagOyD%Gv*L+TezwW zr|4*M1EA?tx4->vuzg+8NYCY6TFss++;HytR(+}HXx~71N`6Fg+A6Igb9vuLPR`hq zAw~I)pMJ;1q|)-Lnp&&?W0+CsLV`_79-19Pl1*KvL%Xc^cBt5R!a$mn?@3}ko2fnY(v@)6N z?Q7YuvJUtWBu!mZ7`d65h7W>YYTb!G(Wsiy_q zrv`_{a168*YgAxfh+jPMqhg4ut_Z8$Xv_ktejvr6!c!U;c|f%=DGnML2+4p|WF&^M zLfcxqR6dc~+=}Bx#YpA)mVS}aepAtK^w*~a&Dea=hwDH+If>zOy@TwftJc-84<)Q1 z;KX1UOEiqRAIFMif0X&5U+1|H6_IFQ1QoSkZP0$D&J!&f0}~^S&XAhhg~VlT74pz# z>Wq(ej8!?h$wC7yW;o*?d;0n*Vv-l7E=WvDS>Ar_(W7Ba9Cc(lNIG<7c=*OqV1n~3wXL{= zzI*46U3+(wrG)$WTl+88;fP>ss0`9t(rt>sP?5xgl_3RWMkFW!Od5b8WLRE78Xi!> z61jwWUe?sH0#RUQ8Xn~1I@1cvhMgC$(&VK3IC%zq@~Jr%WxJEW#l_u!Mc1}X?a-Nq zVVfo)LkG>62~oi+B7@EtGa{trCTP}vhYaADXoE#28GFx zKukb|oST@Ii^UR|%S&Er&qXiH#TkowNi-6H#RL;kF(GaHJPaQ>+so&9&b9+lEwP^I z>h0s@N}P~qRB`u)!1;mD!$UACf!4u@iqm*h1ov0STtrl=F=bK=y=V<)ST}G720(TZ zsm4eI0J5Q_A|G=+_io*O{9J$sgP}lXi-}xlZ)gf@64PB!&ofwZM6??jmwJh6uM3ii)(J0O>(S zl3W(n(bCY^x=|K|W-X!(R8-;6Vh1)i&u^@&C{QccKKSK_?=VofYu{nCL;JS(Zr{DH zw{&5=bHs|g(n>vcKw>x!hcysuUf2mkgz|6T0skfL2vQ?GFjPs%zGtB+U_tX_GC2Xl zJQ#Rt;&DPkO)w3EGi1*3b1@S(bLK2}zd(O47spwSE*^6;>)Phe4+gp)?aHjA5*AWI;wwnus-PMbG3gU|PR1o{+St{u&(ER5rgvc^v<;ky z<3h}~YX}vkN-_3$;Gd`a>-$rEyyitDE=o&IN={yYt&Yn}PYNvu{&D%*z|H%^V@eI~ zfg@$BV?l+O9V7qzS~bk?atl1TfcA)U7`lDRH1JKBoQqB!%WC`F@lF- z!V@+mNI6V%0&#`txx|vl$=*f+bt7$I#*4Lq(uBMhIWN{-F$F4^BXLO#z(BEIh&(#j zlhshpo>5w+7{=B@iYd65NI_*HD8%^-Xt?}|$$sFu%7H}%e#)^?p^MpwDc~S#M=W}} zyD-P37#n>uGD@!$8aRw`6yt!i7Wybb6DTO3J-Bw^?2(SayvSfPtJHMb5{9Ko%ZkpF z?>cko>h*!aJ40MQhgGe5iY08BlUSqn^1dUhP?3%GA3kDAal{x%07oWn25m$h-yI$y zkBMU`Sv16k*i90KJCPwl&3d{DvG&OjKvzAxeg5cvB$WI1_cn#5DX`fW&CUjl0ASNa z9I`QTT8q*cmB3X3A_0QJfNvI4GRto z@b`6JESJUE%}AE{x*~gXbaD;K@`HMtR!B-C`9I|;G=&nQ%cAC^ngpXfgZB0*M~JTm z9JCUx04B1LyDg)IP#ub-3yY=E!BPvk`9)ak1%i`A?hKMV1QZZy%4ZJ;apHAXX}rgD ziA8KuD#oZYvKk+Def!I`>sXF{cUVEKw?j)60P8{&~~I{n+8lY7Op@9 zIR*yfyj>j~?QJareLO(9xp)MBHW%J1lvM3emH?7+V^-3z8q^TjfrkJd`cJH$!4&wH zxTK~744Xt4SnQw_i#WFKuJ)#C3^ifROBt;xt3;=ZCe5)H6~{qz3E$)qz&xksGxW06B#xU1#TKhu?!>| zg>$_7SoRu|66se14B>(_5DqIiu(4)cQ9(gLejd&~JQ%!k>GHt6Y*it4mSPB5-xMOH zv^gd=p5uV(64^2@FWF8>5e9^Ez@VPQ>l6d;3FDXr%2TjSpqB|j79k<#z-V={kpn`5 z2C6(9tjt0XO?>AD#XI}6OT5@Dg#3!2U|4WZ1tbhP78*emIz-bDe70d`N((o&*#HK5 zlL+J@7#ncfh8iyPJ=bF01%}Fr#zc07M+Y9D%0R%3J-#z|{nGcn>z2sMwYs_lZSVOT zIG1qm{)5MVK+%YSo)UloKZVWFAZL(S(aJfHGX^jW7IF0GBxVytQgA8_gN13dU*fG~ z3P&OsESBT!l@K0)=n;g&@pvv=Kgd-*e~Lqzd<3XQuh-YNkVP1=pqK$DpjzNn<0{w$ zg>Whu2?>n`Sg;8gEt0fT@gS6dhilekHYg8_lFL8wbc zSGZ2kQ79_ZX;j413LFQufDYvE4{l$-aI$YhQ}d<+Ki;_e@DX+a4h>U`$;Z4KLxI)q zt?d{u*h~h2-WabqjBW`|#djPEETLKox&<3rQc@Pu(pjTT;PzMuor%F92j)hB1m-xa zE3gZJ=!N2PuyOT5gEgWyM}eEf0>lbb?!W*7Do#7I&J05VFhHmM_Ba^C!h?=j3-*%; z2hk?5#aV*)k8q*3cpLH*R6^3Q(4YX!uzAk$0|*fjLZn$#w1|QM1{Nq#34+Pw%}Nmm zz##Z2eRn{o(j$UErhrDV(H3X|Qqz!BPY}Q@p}T@JG-G4W6-rctsM2W)16}yhp$B(< zyME>Bz%5)18Nr1Sc47qeA_m(KIV-Aah@>RK3k3$50q6aVk>Lo<*Zc5z%?yDyO2_tT;NocWI-x?AogHIx-gvi1#{9$U4 z0Whv%3<$_2(OSEgJOvL^rV%xeUZ77LzPkLZh=BuwfL6T2 z6CpG+8!$G*zCM6-iPY-Hj@ud`V9vE(Hwy!YQ4Y{LApar4*3|K$cKmkFa+MfJ0f3%Xv6R3$i@XbI ziyagxqfcP~qhkPoWgbE<9r(h&D`Z$z`i7QPiUzFCHkbiVS}A8G432xV90}nWFnLudv4>kPFFrUiMCgfU9T-RAFP^8%k_8HQ$84 zt%;T$kTI0#RKvHfUBC5UXjFx@QJ{G-6Ifix>u!YxS{yrS7O64kQA7YBW2{0&HIaCy zSYSZl2(4r}e!|6o0Aezy2N{!#ii8;ggCI64ytS5O87P_~sIH47dKc9Mc>`^*Cd8Vi zQYjE)Cmp?c8Z74-k>T?v$qLCN5Q)kEXpL{)$hyM;sU4;nzJAOt$wDw6i#HFs5Z*Xd t+LS1Pld{Z}Xf6NBN(=HlEdH`Dp2uD{wfk!?-Z%T;Uz*#Faq=^3{s&nrSxo={ literal 0 HcmV?d00001 diff --git a/test/renders/ringlight.rad b/test/renders/ringlight.rad new file mode 100644 index 000000000..75a70f369 --- /dev/null +++ b/test/renders/ringlight.rad @@ -0,0 +1,32 @@ +# RCSid $Id: ringlight.rad,v 1.1 2024/12/09 00:44:29 greg Exp $ +# +# A single, point-like ceiling source to generate sharp shadows +# +# Patterns around perimeter make rings on walls +# + +void brightfunc ringing +2 if(Dz-.75,1,.5+.5*cos(40*(acos(Dz)-acos(.75)))) . +0 +0 + +ringing light superbright +0 +0 +3 7500 7500 7500 + +superbright ring recessed_fixture +0 +0 +8 + -4 2.5 2.99 + 0 0 -1 + 0 .02 + +black_outside cylinder mount +0 +0 +7 + -4 2.5 2.99 + -4 2.5 3.01 + .02 diff --git a/test/renders/wgmdf.rif b/test/renders/wgmdf.rif new file mode 100644 index 000000000..1a8ed51d7 --- /dev/null +++ b/test/renders/wgmdf.rif @@ -0,0 +1,28 @@ +# RCSid $Id: wgmdf.rif,v 1.1 2024/12/09 00:44:29 greg Exp $ +# +# Test of WGMDfunc material +# + +OCTREE = wgmdf.oct + +materials = basic.mat +scene = diorama_walls.rad closed_end.rad front_cap.rad +scene = ringlight.rad +scene = gymbal2.rad + +ZONE = I -8 0 0 5 0 3 + +EXP = +4 + +RES = 1024 +QUAL = Med +AMB = wgmdf.amb +IND = 1 +VAR = High +DET = High +PEN = False + +render = @render.opt + +view= def -vp -2.60899 4.62728 2.52391 -vd -1.11057 -1.64932 -1.10768 \ + -vh 46.6777 -vv 46.6777