/* hierefine.c: hierarchical refinement */ #include "galerkinP.h" #include "formfactor.h" #include "shaftculling.h" #include "statistics.h" #include "error.h" #include "ui.h" #include "basis.h" #include "geom.h" #include "cluster.h" #include "coefficients.h" /* #define DEBUG */ typedef int BOOLEAN; /* ************* Shaftculling stuff for hierarchical refinement *************** */ static GEOMLIST *candlist; /* candidate occluder list for a pair of patches. */ /* Does shaftculling between elements in a link (if the user asked for it). * Updates the global candlist. Returns the old candlist, so it can be restored * later (using UnCull()). */ static GEOMLIST *Cull(INTERACTION *link) { GEOMLIST *ocandlist = candlist; if (ocandlist == (GEOMLIST *)NULL) return (GEOMLIST *)NULL; if (gal.shaftcullmode == DO_SHAFTCULLING_FOR_REFINEMENT || gal.shaftcullmode == ALWAYS_DO_SHAFTCULLING) { SHAFT shaft, *the_shaft; if (gal.exact_visibility && !IsCluster(link->rcv) && !IsCluster(link->src)) { POLYGON rcvpoly, srcpoly; the_shaft = ConstructPolygonToPolygonShaft(ElementPolygon(link->rcv, &rcvpoly), ElementPolygon(link->src, &srcpoly), &shaft); } else { BOUNDINGBOX srcbounds, rcvbounds; the_shaft = ConstructShaft(ElementBounds(link->rcv, rcvbounds), ElementBounds(link->src, srcbounds), &shaft); } if (!the_shaft) { Error("Cull", "Couldn't construct shaft"); return ocandlist; } if (IsCluster(link->rcv)) ShaftDontOpen(&shaft, link->rcv->pog.geom); else ShaftOmit(&shaft, (GEOM *)link->rcv->pog.patch); if (IsCluster(link->src)) ShaftDontOpen(&shaft, link->src->pog.geom); else ShaftOmit(&shaft, (GEOM *)link->src->pog.patch); if (ocandlist == ClusteredWorld) candlist = ShaftCullGeom(ClusteredWorldGeom, &shaft, (GEOMLIST *)NULL); else candlist = DoShaftCulling(ocandlist, &shaft, (GEOMLIST *)NULL); } return ocandlist; } /* Destroys the current candlist and restores the previous one (passed as * an argument). */ static void UnCull(GEOMLIST *ocandlist) { if (gal.shaftcullmode == DO_SHAFTCULLING_FOR_REFINEMENT || gal.shaftcullmode == ALWAYS_DO_SHAFTCULLING) { FreeCandidateList(candlist); } candlist = ocandlist; } /* ********************* Link error estimation *********************** */ static double ColorToError(COLOR rad) { RGB rgb; ColorToRGB(rad, &rgb); return RGBMAXCOMPONENT(rgb); } /* Instead of computing the approximation etc... error in radiance or power * error and comparing with a radiance resp. power threshold after weighting * with importance, we modify the threshold and always compare with the error * in radiance norm as if no importance is used. This enables us to skip * estimation of some error terms if it turns out that they are not necessary * anymore. */ static double LinkErrorThreshold(INTERACTION *link, double rcv_area) { double threshold = 0.; switch (gal.error_norm) { case RADIANCE_ERROR: threshold = ColorToError(max_selfemitted_radiance) * gal.rel_link_error_threshold; break; case POWER_ERROR: threshold = ColorToError(max_selfemitted_power) * gal.rel_link_error_threshold / (M_PI * rcv_area); break; default: Fatal(2, "EvaluateInteraction", "Invalid error norm"); } /* Weight the error with the potential of the receiver in case of view-potential * driven gathering (potential is used in a totally different way in shooting * algorithms). It is assumed that the average direct potential is about half * of the maximum direct potential. This way, about an equal precision is * obtained in the visible parts of the scene whether or not importance is used. */ /* instead of weighting the error, we weight the threshold with the inverse. */ if (gal.importance_driven && (gal.iteration_method == JACOBI || gal.iteration_method == GAUSS_SEIDEL)) threshold /= 2. * link->rcv->potential.f / max_direct_potential; return threshold; } /* Compute an estimate for the approximation error that would be made if the * candidate link were used for light transport. Use the sources unshot * radiance when doing shooting and the total radiance when gathering. */ static double ApproximationError(INTERACTION *link, COLOR srcrho, COLOR rcvrho, double rcv_area) { COLOR error, srcrad; double approx_error=0., approx_error2; switch (gal.iteration_method) { case GAUSS_SEIDEL: case JACOBI: if (IsCluster(link->src) && link->src!=link->rcv) srcrad = MaxClusterRadiance(link->src); /* SourceClusterRadiance(link); */ else srcrad = link->src->radiance[0]; COLORPRODSCALED(rcvrho, link->deltaK.f, srcrad, error); COLORABS(error, error); approx_error = ColorToError(error); break; case SOUTHWELL: if (IsCluster(link->src) && link->src!=link->rcv) srcrad = SourceClusterRadiance(link); /* returns unshot radiance for shooting */ else srcrad = link->src->unshot_radiance[0]; COLORPRODSCALED(rcvrho, link->deltaK.f, srcrad, error); COLORABS(error, error); approx_error = ColorToError(error); if (gal.importance_driven && IsCluster(link->rcv)) { /* make sure the link is also suited for transport of unshot potential * from source to receiver. Note that it makes no sense to * subdivide receiver patches (potential is only used to help * choosing a radiance shooting patch. */ approx_error2 = (ColorToError(srcrho) * link->deltaK.f * link->src->unshot_potential.f); /* compare potential error w.r.t. maximum direct potential or importance * instead of selfemitted radiance or power. */ switch (gal.error_norm) { case RADIANCE_ERROR: approx_error2 *= ColorToError(max_selfemitted_radiance) / max_direct_potential; break; case POWER_ERROR: approx_error2 *= ColorToError(max_selfemitted_power) / M_PI / max_direct_importance; break; } if (approx_error2 > approx_error) approx_error = approx_error2; } break; default: Fatal(-1, "ApproximationError", "Invalid iteration method"); } return approx_error; } /* Estimates the error due to the variation of the source cluster radiance * as seen from a number of sample points on the receiver element. Especially * when intra source cluster visibility is handled with a Z-buffer algorithm, * this operation is quite expensive and should be avoided when not stricktly * necessary. */ static double SourceClusterRadianceVariationError(INTERACTION *link, COLOR rcvrho, double rcv_area) { POINT rcverts[8]; int i, nrcverts; COLOR minsrcrad, maxsrcrad, error; double K; K = (link->nsrc == 1 && link->nrcv == 1) ? link->K.f : link->K.p[0]; if (K == 0. || COLORNULL(rcvrho) || COLORNULL(link->src->radiance[0])) { /* receiver reflectivity or coupling coefficient or source radiance * is zero */ return 0.; } nrcverts = ElementVertices(link->rcv, rcverts); COLORSETMONOCHROME(minsrcrad, HUGE); COLORSETMONOCHROME(maxsrcrad, -HUGE); for (i=0; isrc, rcverts[i]); COLORMIN(minsrcrad, rad, minsrcrad); COLORMAX(maxsrcrad, rad, maxsrcrad); } COLORSUBTRACT(maxsrcrad, minsrcrad, error); COLORPRODSCALED(rcvrho, K/rcv_area, error, error); COLORABS(error, error); return ColorToError(error); } /* Evaluates the interaction and returns a code telling whether it is accurate enough * for computing light transport, or what to do in order to reduce the * (estimated) error in the most efficient way. This is the famous oracle function * which is so crucial for efficient hierarchical refinement. * * See DOC/galerkin.text. */ typedef enum {ACCURATE_ENOUGH, REGULAR_SUBDIVIDE_SOURCE, REGULAR_SUBDIVIDE_RECEIVER, SUBDIVIDE_SOURCE_CLUSTER, SUBDIVIDE_RECEIVER_CLUSTER, ENLARGE_RECEIVER_BASIS} INTERACTION_EVALUATION_CODE; static INTERACTION_EVALUATION_CODE EvaluateInteraction(INTERACTION *link) { COLOR srcrho, rcvrho; double error, threshold, rcv_area, min_area; INTERACTION_EVALUATION_CODE code = ACCURATE_ENOUGH; if (!gal.hierarchical) return ACCURATE_ENOUGH; /* simply don't refine. */ /* determine receiver area (projected visible area for a receiver cluster) * and reflectivity. */ if (IsCluster(link->rcv)) { COLORSETMONOCHROME(rcvrho, 1.); rcv_area = ReceiverClusterArea(link); } else { rcvrho = REFLECTIVITY(link->rcv->pog.patch); rcv_area = link->rcv->area; } /* determine source reflectivity. */ if (IsCluster(link->src)) { COLORSETMONOCHROME(srcrho, 1.); } else srcrho = REFLECTIVITY(link->src->pog.patch); /* determine error estimate and error threshold */ threshold = LinkErrorThreshold(link, rcv_area); error = ApproximationError(link, srcrho, rcvrho, rcv_area); if (IsCluster(link->src) && error < threshold && gal.clustering_strategy != ISOTROPIC) error += SourceClusterRadianceVariationError(link, rcvrho, rcv_area); /* Minimal element area for which subdivision is allowed. */ min_area = total_area * gal.rel_min_elem_area; code = ACCURATE_ENOUGH; if (error > threshold) { /* A very simple but robust subdivision strategy: subdivide the * largest of the two elements in order to reduce the error. */ if ((!(IsCluster(link->src) && IsLightSource(link->src))) && (rcv_area > link->src->area)) { if (rcv_area > min_area) { if (IsCluster(link->rcv)) code = SUBDIVIDE_RECEIVER_CLUSTER; else code = REGULAR_SUBDIVIDE_RECEIVER; } } else { if (IsCluster(link->src)) code = SUBDIVIDE_SOURCE_CLUSTER; else if (link->src->area > min_area) code = REGULAR_SUBDIVIDE_SOURCE; } } return code; } /* ****************** Light transport computation ********************* */ /* Computes light transport over the given interaction, which is supposed to be * accurate enough for doing so. Renormalisation and reflection and such is done * once for all accumulated received radiance during push-pull. */ static void ComputeLightTransport(INTERACTION *link) { COLOR *srcrad, *rcvrad, avsrclusrad; int alpha, beta, a, b; /* Update the number of effectively used radiance coefficients on the * receiver element. */ a = MIN(link->nrcv, link->rcv->basis_size); b = MIN(link->nsrc, link->src->basis_size); if (a > link->rcv->basis_used) link->rcv->basis_used = a; if (b > link->src->basis_used) link->src->basis_used = b; if (gal.iteration_method == SOUTHWELL) srcrad = link->src->unshot_radiance; else srcrad = link->src->radiance; if (IsCluster(link->src) && link->src!=link->rcv) { avsrclusrad = SourceClusterRadiance(link); srcrad = &avsrclusrad; } if (IsCluster(link->rcv) && link->src!=link->rcv) { ClusterGatherRadiance(link, srcrad); } else { rcvrad = link->rcv->received_radiance; if (link->nrcv==1 && link->nsrc==1) { COLORADDSCALED(rcvrad[0], link->K.f, srcrad[0], rcvrad[0]); } else { for (alpha=0; alphaK.p[alpha*link->nsrc + beta], srcrad[beta], rcvrad[alpha]); } } if (gal.importance_driven) { float K = ((link->nrcv==1 && link->nsrc==1) ? link->K.f : link->K.p[0]); COLOR rcvrho, srcrho; if (gal.iteration_method == GAUSS_SEIDEL || gal.iteration_method == JACOBI) { if (IsCluster(link->rcv)) { COLORSETMONOCHROME(rcvrho, 1.); } else rcvrho = REFLECTIVITY(link->rcv->pog.patch); link->src->received_potential.f += K * ColorToError(rcvrho) * link->rcv->potential.f; } else if (gal.iteration_method == SOUTHWELL) { if (IsCluster(link->src)) { COLORSETMONOCHROME(srcrho, 1.); } else srcrho = REFLECTIVITY(link->src->pog.patch); link->rcv->received_potential.f += K * ColorToError(srcrho) * link->src->unshot_potential.f; } else Fatal(-1, "ComputeLightTransport", "Hela hola did you introduce a new iteration method or so??"); } } /* ********************** Refinement procedures ************************* */ /* Computes the formfactor and error esitmation coefficients. If the formfactor * is not zero, the data is filled in in the INTERACTION struct pointed to by 'link' * and TRUE is returned. If the elements don't itneract, FALSE is returned. */ int CreateSubdivisionLink(ELEMENT *rcv, ELEMENT *src, INTERACTION *link) { link->rcv = rcv; link->src = src; /* Always a constant approximation on cluster elements. */ if (IsCluster(link->rcv)) link->nrcv = 1; else link->nrcv = rcv->basis_size; if (IsCluster(link->src)) link->nsrc = 1; else link->nsrc = src->basis_size; AreaToAreaFormFactor(link, candlist); if (gal.wake_up) { ProcessWaitingEvents(); gal.wake_up = FALSE; } return (link->vis != 0); } /* Duplicates the INTERACTION data and stores it with the receivers interactions * if doing gathering and with the source for shooting. */ static void StoreInteraction(INTERACTION *link) { ELEMENT *src=link->src, *rcv=link->rcv; if (gal.iteration_method == SOUTHWELL) src->interactions = InteractionListAdd(src->interactions, InteractionDuplicate(link)); else rcv->interactions = InteractionListAdd(rcv->interactions, InteractionDuplicate(link)); } static int RefineRecursive(INTERACTION *link); /* forward decl. */ /* Subdivides the source element, creates subinteractions and refines. If the * subinteractions do not need to be refined any further, they are added to * either the sources, either the receivers interaction list, depending on the * iteration method being used. This routine always returns TRUE indicating that * the passed interaction is always replaced by lower level interactions. */ static int RegularSubdivideSource(INTERACTION *link) { GEOMLIST *ocandlist = Cull(link); ELEMENT *src=link->src, *rcv=link->rcv; int i; RegularSubdivideElement(src); for (i=0; i<4; i++) { ELEMENT *child = src->regular_subelements[i]; INTERACTION subinteraction; float ff[MAXBASISSIZE*MAXBASISSIZE]; /* #ident <<< WMP */ #ifdef WMP_WEIGHTS float KF[MAXBASISSIZE]; subinteraction.FK = KF; #endif /* #ident >>> WMP */ subinteraction.K.p = ff; /* temporary storage for the formfactors */ /* subinteraction.deltaK.p = */ if (CreateSubdivisionLink(rcv, child, &subinteraction)) { if (!RefineRecursive(&subinteraction)) StoreInteraction(&subinteraction); } } UnCull(ocandlist); return TRUE; } /* Same, but subdivides the receiver element. */ static int RegularSubdivideReceiver(INTERACTION *link) { GEOMLIST *ocandlist = Cull(link); ELEMENT *src=link->src, *rcv=link->rcv; int i; RegularSubdivideElement(rcv); for (i=0; i<4; i++) { INTERACTION subinteraction; float ff[MAXBASISSIZE*MAXBASISSIZE]; ELEMENT *child = rcv->regular_subelements[i]; /* #ident <<< WMP */ #ifdef WMP_WEIGHTS float KF[MAXBASISSIZE]; subinteraction.FK = KF; #endif /* #ident >>> WMP */ subinteraction.K.p = ff; if (CreateSubdivisionLink(child, src, &subinteraction)) { if (!RefineRecursive(&subinteraction)) StoreInteraction(&subinteraction); } } UnCull(ocandlist); return TRUE; } /* Replace the interaction by interactions with the subclusters of the source, * which is a cluster. */ static int SubdivideSourceCluster(INTERACTION *link) { GEOMLIST *ocandlist = Cull(link); ELEMENT *src=link->src, *rcv=link->rcv; ELEMENTLIST *subcluslist; for (subcluslist = src->irregular_subelements; subcluslist; subcluslist=subcluslist->next) { ELEMENT *child = subcluslist->element; INTERACTION subinteraction; float ff[MAXBASISSIZE*MAXBASISSIZE]; /* #ident <<< WMP */ #ifdef WMP_WEIGHTS float KF[MAXBASISSIZE]; subinteraction.FK = KF; #endif /* #ident >>> WMP */ subinteraction.K.p = ff; /* temporary storage for the formfactors */ /* subinteraction.deltaK.p = */ if (!IsCluster(child)) { PATCH *the_patch = child->pog.patch; if ((IsCluster(rcv) && BoundsBehindPlane(GeomBounds(rcv->pog.geom), &the_patch->normal, the_patch->plane_constant)) || (!IsCluster(rcv) && !Facing(rcv->pog.patch, the_patch))) { continue; } } if (CreateSubdivisionLink(rcv, child, &subinteraction)) { if (!RefineRecursive(&subinteraction)) StoreInteraction(&subinteraction); } } UnCull(ocandlist); return TRUE; } /* Replace the interaction by interactions with the subclusters of the receiver, * which is a cluster. */ static int SubdivideReceiverCluster(INTERACTION *link) { GEOMLIST *ocandlist = Cull(link); ELEMENT *src=link->src, *rcv=link->rcv; ELEMENTLIST *subcluslist; for (subcluslist=rcv->irregular_subelements; subcluslist; subcluslist=subcluslist->next) { ELEMENT *child = subcluslist->element; INTERACTION subinteraction; float ff[MAXBASISSIZE*MAXBASISSIZE]; /* #ident <<< WMP */ #ifdef WMP_WEIGHTS float KF[MAXBASISSIZE]; subinteraction.FK = KF; #endif /* #ident >>> WMP */ subinteraction.K.p = ff; if (!IsCluster(child)) { PATCH *the_patch = child->pog.patch; if ((IsCluster(src) && BoundsBehindPlane(GeomBounds(src->pog.geom), &the_patch->normal, the_patch->plane_constant)) || (!IsCluster(src) && !Facing(src->pog.patch, the_patch))) { continue; } } if (CreateSubdivisionLink(child, src, &subinteraction)) { if (!RefineRecursive(&subinteraction)) StoreInteraction(&subinteraction); } } UnCull(ocandlist); return TRUE; } /* Recursievly refines the interaction. Returns TRUE if the interaction was * effectively refined, so the specified interaction can be deleted. Returns FALSE * if the interaction was not refined and is to be retained. If the interaction * does not need to be refined, light transport over the interaction is computed. */ int RefineRecursive(INTERACTION *link) { int refined = FALSE; switch (EvaluateInteraction(link)) { case ACCURATE_ENOUGH: ComputeLightTransport(link); refined = FALSE; break; case REGULAR_SUBDIVIDE_SOURCE: refined = RegularSubdivideSource(link); break; case REGULAR_SUBDIVIDE_RECEIVER: refined = RegularSubdivideReceiver(link); break; case SUBDIVIDE_SOURCE_CLUSTER: refined = SubdivideSourceCluster(link); break; case SUBDIVIDE_RECEIVER_CLUSTER: refined = SubdivideReceiverCluster(link); break; default: Fatal(2, "RefineRecursive", "Invalid result from EvaluateInteraction()"); } return refined; } void RefineInteraction(INTERACTION *link) { candlist = ClusteredWorld; /* candidate occluder list for a pair of patches. */ if (gal.exact_visibility && link->vis == 255) candlist = (GEOMLIST *)NULL; /* we know for sure that there is full visibility */ if (RefineRecursive(link)) { if (gal.iteration_method == SOUTHWELL) link->src->interactions = InteractionListRemove(link->src->interactions, link); else link->rcv->interactions = InteractionListRemove(link->rcv->interactions, link); InteractionDestroy(link); } if (gal.wake_up) { ProcessWaitingEvents(); gal.wake_up = FALSE; } } /* Refines and computes light transport over all interactions of the given * toplevel element. */ void RefineInteractions(ELEMENT *top) { /* interactions will only be replaced by lower level interactions. We try refinement * beginning at the lowest levels in the hierarchy and working upwards to * prevent already refined interactions from being tested for refinement * again. */ ITERATE_IRREGULAR_SUBELEMENTS(top, RefineInteractions); ITERATE_REGULAR_SUBELEMENTS(top, RefineInteractions); /* Iterate over the interactions. Interactions that are refined are removed from the * list in RefineInteraction(). ListIterate allows the current element to be deleted. */ InteractionListIterate(top->interactions, RefineInteraction); }