/* shaftculling.c */ /* * Shaft culling ala Haines, E. A. and Wallace, J. R. "Shaft culling for * efficient ray-traced radiosity", 2nd Eurographics Workshop * on Rendering, Barcelona, Spain, may 1991 */ #include #include "shaftculling.h" #include "patchlist_geom.h" #include "geom.h" #include "patch.h" #include "error.h" #include "Boolean.h" #include "geometry3d.h" /* default strategy is "overlap open", which was * the most efficient strategy in the tests I did. */ static SHAFTCULLSTRATEGY strategy = OVERLAP_OPEN; /* Specify a shaftculling strategy. Defaults is OVERLAP_OPEN. Returns * previous strategy. */ SHAFTCULLSTRATEGY SetShaftCullStrategy(SHAFTCULLSTRATEGY newstrategy) { SHAFTCULLSTRATEGY oldstrategy = strategy; strategy = newstrategy; return oldstrategy; } void ShaftOmit(SHAFT *shaft, GEOM *geom) { shaft->omit[shaft->nromit++] = geom; } void ShaftDontOpen(SHAFT *shaft, GEOM *geom) { shaft->dontopen[shaft->nrdontopen++] = geom; } /* constructs a shaft for two given bounding boxes */ SHAFT *ConstructShaft(float *ref1, float *ref2, SHAFT *shaft) { int i, j, hasminmax1[6], hasminmax2[6]; SHAFTPLANE *plane; shaft->nromit = shaft->nrdontopen = 0; shaft->cut = FALSE; shaft->ref1 = ref1; shaft->ref2 = ref2; /* midpoints of the reference boxes define a line that is guaranteed * to lay within the shaft. */ shaft->center1.x = 0.5 * (ref1[MIN_X] + ref1[MAX_X]); shaft->center1.y = 0.5 * (ref1[MIN_Y] + ref1[MAX_Y]); shaft->center1.z = 0.5 * (ref1[MIN_Z] + ref1[MAX_Z]); shaft->center2.x = 0.5 * (ref2[MIN_X] + ref2[MAX_X]); shaft->center2.y = 0.5 * (ref2[MIN_Y] + ref2[MAX_Y]); shaft->center2.z = 0.5 * (ref2[MIN_Z] + ref2[MAX_Z]); for (i=0; i<6; i++) { hasminmax1[i] = hasminmax2[i] = 0; } /* create extent box of both items and keep track which coordinates of which * box become the minimum or maximum */ for (i=MIN_X; i<=MIN_Z; i++) { if (shaft->ref1[i] < shaft->ref2[i]) { shaft->extent[i] = shaft->ref1[i]; hasminmax1[i] = 1; } else { shaft->extent[i] = shaft->ref2[i]; if (!FLOATEQUAL(shaft->ref1[i], shaft->ref2[i], EPSILON)) hasminmax2[i] = 1; } } for (i=MAX_X; i<=MAX_Z; i++) { if (shaft->ref1[i] > shaft->ref2[i]) { shaft->extent[i] = shaft->ref1[i]; hasminmax1[i] = 1; } else { shaft->extent[i] = shaft->ref2[i]; if (!FLOATEQUAL(shaft->ref1[i], shaft->ref2[i], EPSILON)) hasminmax2[i] = 1; } } /* create plane set */ plane = &shaft->plane[0]; for (i=0; i<6; i++) { if (!hasminmax1[i]) continue; for (j=0; j<6; j++) { float u1, u2, v1, v2, du, dv; int a=(i%3), b=(j%3); /* directions */ if (!hasminmax2[j] || a == b) /*same direction*/ continue; u1 = shaft->ref1[i]; /* coords. defining the plane */ v1 = shaft->ref1[j]; u2 = shaft->ref2[i]; v2 = shaft->ref2[j]; if ((i<=MIN_Z && j<=MIN_Z) || (i>=MAX_X && j>=MAX_X)) { du = v2-v1; dv = u1-u2; } else { /* normal must point outwards shaft */ du = v1-v2; dv = u2-u1; } plane->n[a] = du; plane->n[b] = dv; plane->n[3-a-b] = 0.; plane->d = -(du*u1 + dv*v1); plane->coord_offset[0] = plane->n[0] > 0. ? MIN_X : MAX_X; plane->coord_offset[1] = plane->n[1] > 0. ? MIN_Y : MAX_Y; plane->coord_offset[2] = plane->n[2] > 0. ? MIN_Z : MAX_Z; plane++; } } shaft->planes = plane - &shaft->plane[0]; return shaft; } /* some constants describing the position of some item with respect to a plane * or a shaft. */ #define INSIDE -1 #define OVERLAP 0 #define OUTSIDE 1 #define COPLANAR 2 /* Tests a polygon w.r.t. the plane defined by the given normal and plane * constant. Returns INSIDE if the polygon is totally on the negative side of * the plane, OUTSIDE if the polygon on all on the positive side, OVERLAP * if the polygon is cut by the plane and COPLANAR if the polygon lays on the * plane within tolerance distance d*EPSILON. */ static int TestPolygonWrtPlane(POLYGON *poly, DVECTOR *normal, double d) { int i, out, in; /* out = there are points on the positive side of the plane */ /* in = there are points on the negative side of the plane */ out = in = FALSE; for (i=0; inrvertices; i++) { double e = VECTORDOTPRODUCT(*normal, poly->vertex[i]) + d, tolerance = fabs(d) * EPSILON + VECTORTOLERANCE(poly->vertex[i]); out |= (e > tolerance); in |= (e < -tolerance); if (out && in) return OVERLAP; } return (out ? OUTSIDE : (in ? INSIDE : COPLANAR)); } /* Verifies whether the polygon is on the given side of the plane. Returns TRUE if * so, and FALSE if not. */ int VerifyPolygonWrtPlane(POLYGON *poly, DVECTOR *normal, double d, int side) { int i, out, in; out = in = FALSE; for (i=0; inrvertices; i++) { double e = VECTORDOTPRODUCT(*normal, poly->vertex[i]) + d, tolerance = fabs(d) * EPSILON + VECTORTOLERANCE(poly->vertex[i]); out |= (e > tolerance); if (out && (side==INSIDE || side==COPLANAR)) return FALSE; in |= (e < -tolerance); if (in && (side==OUTSIDE || side==COPLANAR || (out && side!=OVERLAP))) return FALSE; } if (in) { if (out) { if (side == OVERLAP) return TRUE; } else { if (side == INSIDE) return TRUE; } } else { if (out) { if (side == OUTSIDE) return TRUE; } else { if (side == COPLANAR) return TRUE; } } return FALSE; } /* Tests the position of a point w.r.t. a plane. Returns OUTSIDE if the point is * on the positive side of the plane, INSIDE if on the negative side, and COPLANAR * if the point is on the plane within tolerance distance d*EPSILON. */ int TestPointWrtPlane(POINT *p, DVECTOR *normal, double d) { double e, tolerance=fabs(d*EPSILON) + VECTORTOLERANCE(*p); e = VECTORDOTPRODUCT(*normal, *p) + d; if (e < -tolerance) return INSIDE; if (e > +tolerance) return OUTSIDE; return COPLANAR; } /* Compare to shaft planes. Returns 0 if they are the same and -1 or +1 * if not (can be used for sorting the planes. It is assumed that the plane normals * are normalized! */ static int CompareShaftPlanes(SHAFTPLANE *p1, SHAFTPLANE *p2) { double tolerance; /* compare components of plane normal (normalized vector, so components * are in the range [-1,1]. */ if (p1->n[0] < p2->n[0] - EPSILON) return -1; else if (p1->n[0] > p2->n[0] + EPSILON) return +1; if (p1->n[1] < p2->n[1] - EPSILON) return -1; else if (p1->n[1] > p2->n[1] + EPSILON) return +1; if (p1->n[2] < p2->n[2] - EPSILON) return -1; else if (p1->n[2] > p2->n[2] + EPSILON) return +1; /* compare plane constants. */ tolerance = fabs(MAX(p1->d, p2->d) * EPSILON); if (p1->d < p2->d - tolerance) return -1; else if (p1->d > p2->d + tolerance) return +1; return 0; } /* Plane is a pointer to the shaft plane defined in shaft. This routine will return * TRUE if the plane differs from all previous defined planes. */ static int UniqueShaftPlane(SHAFT *shaft, SHAFTPLANE *plane) { SHAFTPLANE *ref; for (ref=&shaft->plane[0]; ref != plane; ref++) if (CompareShaftPlanes(ref, plane)==0) return FALSE; return TRUE; } /* fills in normal and plane constant, as will as the coord_offset arameters. */ static void FillInPlane(SHAFTPLANE *plane, double nx, double ny, double nz, double d) { plane->n[0] = nx; plane->n[1] = ny; plane->n[2] = nz; plane->d = d; plane->coord_offset[0] = plane->n[0] > 0. ? MIN_X : MAX_X; plane->coord_offset[1] = plane->n[1] > 0. ? MIN_Y : MAX_Y; plane->coord_offset[2] = plane->n[2] > 0. ? MIN_Z : MAX_Z; } /* Construct the planes determining the shaft that use edges of p1 and vertices * of p2. */ static void ConstructPolygonToPolygonPlanes(POLYGON *p1, POLYGON *p2, SHAFT *shaft) { SHAFTPLANE *plane = &shaft->plane[shaft->planes]; POINT *cur, *next, *other; DVECTOR normal; double d, norm; int i, j, k, side, planes_found_for_edge, max_planes_per_edge; /* test p2 wrt plane of p1 */ VECTORCOPY(p1->normal, normal); /* convert to double precision */ switch (TestPolygonWrtPlane(p2, &normal, p1->plane_constant)) { case INSIDE: /* polygon p2 is on the negative side of the plane of p1. The plane of p1 is * a shaft plane and there will be at most one shaft plane per edge of p1. */ FillInPlane(plane, p1->normal.x, p1->normal.y, p1->normal.z, p1->plane_constant); if (UniqueShaftPlane(shaft, plane)) plane++; max_planes_per_edge = 1; break; case OUTSIDE: /* like above, except that p2 is on the positive side of the plane of p1, so * we have to invert normal and plane constant. */ FillInPlane(plane, -p1->normal.x, -p1->normal.y, -p1->normal.z, -p1->plane_constant); if (UniqueShaftPlane(shaft, plane)) plane++; max_planes_per_edge = 1; break; case OVERLAP: /* the plane of p1 cuts p2. It is not a shaft plane and there may be up to two * shaft planes for each edge of p1. */ max_planes_per_edge = 2; break; default: /* COPLANAR */ /* degenerate case that should never happen. If it does, it will result * in a shaft with no planes and just a thin bounding box containing the coplanar * faces. */ return; } for (i=0; inrvertices; i++) { /* for each edge of p1 */ cur = &p1->vertex[i]; next = &p1->vertex[(i+1)%p1->nrvertices]; planes_found_for_edge = 0; for (j=0; jnrvertices && planes_found_for_edge < max_planes_per_edge; j++) { /* for each vertex of p2 */ other = &p2->vertex[j]; /* compute normal and plane constant of the plane formed by cur, next and * other */ VECTORTRIPLECROSSPRODUCT(*cur, *next, *other, normal); norm = VECTORNORM(normal); if (norm < EPSILON) continue; /* colinear vertices, try next vertex on p2 */ VECTORSCALEINVERSE(norm, normal, normal); d = -VECTORDOTPRODUCT(normal, *cur); /* Test position of p1 w.r.t. the constructed plane. Skip the vertices * that were used to construct the plane. */ k = (i+2)%p1->nrvertices; side = TestPointWrtPlane(&p1->vertex[k], &normal, d); for (k=(i+3)%p1->nrvertices; k != i; k=(k+1)%p1->nrvertices) { int nside = TestPointWrtPlane(&p1->vertex[k], &normal, d); if (side == COPLANAR) side = nside; else if (nside != COPLANAR) if (side != nside) /* side==INSIDE and nside==OUTSIDE or vice versa, */ side = OVERLAP; /* or side==OVERLAP already. May happen due to round */ } /* off error e.g. */ if (side != INSIDE && side != OUTSIDE) continue; /* not a valid candidate shaft plane */ /* Verify whether p2 is on the same side of the constructed plane. If so, * the plane is a candidate shaft plane and will be added to the list if * it is unique. */ if (VerifyPolygonWrtPlane(p2, &normal, d, side)) { if (side==INSIDE) /* p1 and p2 are on the negative side as it should be */ FillInPlane(plane, normal.x, normal.y, normal.z, d); else FillInPlane(plane, -normal.x, -normal.y, -normal.z, -d); if (UniqueShaftPlane(shaft, plane)) plane++; planes_found_for_edge++; } } } shaft->planes = plane - &shaft->plane[0]; } /* Constructs a shaft enclosing the two given polygons. */ SHAFT *ConstructPolygonToPolygonShaft(POLYGON *p1, POLYGON *p2, SHAFT *shaft) { int i; /* no "reference" bounding boxes to test with */ shaft->ref1 = shaft->ref2 = (float *)NULL; /* shaft extent = bounding box containing the bounding boxes of the * patches */ BoundsCopy(p1->bounds, shaft->extent); BoundsEnlarge(shaft->extent, p2->bounds); /* nothing (yet) to omit */ shaft->omit[0] = shaft->omit[1] = (GEOM *)NULL; shaft->dontopen[0] = shaft->dontopen[1] = (GEOM *)NULL; shaft->nromit = shaft->nrdontopen = 0; shaft->cut = FALSE; /* center points of polygons define a line that is guaranteed to lay * inside the shaft. */ shaft->center1 = p1->vertex[0]; for (i=1; inrvertices; i++) { VECTORADD(shaft->center1, p1->vertex[i], shaft->center1); } VECTORSCALEINVERSE((float)p1->nrvertices, shaft->center1, shaft->center1); shaft->center2 = p2->vertex[0]; for (i=1; inrvertices; i++) { VECTORADD(shaft->center2, p2->vertex[i], shaft->center2); } VECTORSCALEINVERSE((float)p2->nrvertices, shaft->center2, shaft->center2); /* determine the shaft planes */ shaft->planes = 0; ConstructPolygonToPolygonPlanes(p1, p2, shaft); ConstructPolygonToPolygonPlanes(p2, p1, shaft); return shaft; } /* Tests a bounding volume against the shaft: returns INSIDE if the bounding volume * is inside the shaft, OVERLAP if it overlaps, OUTSIDE if it is otside the shaft */ int ShaftBoxTest(float *bounds, SHAFT *shaft) { int i; SHAFTPLANE *plane; /* test against extent box */ if (DisjunctBounds(bounds, shaft->extent)) return OUTSIDE; /* test against plane set: if nearest corner of the bounding box is on or * outside any shaft plane, the object is outside the shaft */ for (i=0, plane=&shaft->plane[0]; iplanes; i++, plane++) if (plane->n[0] * bounds[plane->coord_offset[0]] + plane->n[1] * bounds[plane->coord_offset[1]] + plane->n[2] * bounds[plane->coord_offset[2]] + plane->d > -fabs(plane->d * EPSILON)) return OUTSIDE; /* test against reference items */ if ((shaft->ref1 && !DisjunctBounds(bounds, shaft->ref1)) || (shaft->ref2 && !DisjunctBounds(bounds, shaft->ref2))) return OVERLAP; /* if the bounding box survides all previous tests, it must overlap or be inside the * shaft. If the farest corner of the bounding box is outside any shaft-plane, it * overlaps the shaft, otherwise it is inside the shaft */ for (i=0, plane=&shaft->plane[0]; iplanes; i++, plane++) if (plane->n[0] * bounds[(plane->coord_offset[0]+3)%6] + plane->n[1] * bounds[(plane->coord_offset[1]+3)%6] + plane->n[2] * bounds[(plane->coord_offset[2]+3)%6] + plane->d > fabs(plane->d * EPSILON)) return OVERLAP; return INSIDE; } /* Tests the patch against the shaft: returns INSIDE, OVERLAP or OUTSIDE according * to whether the patch is fully inside the shaft, overlapping it, or fully outside. If it is detected that the patch fully occludes the shaft, shaft->cut is * set to TRUE, indicating that there is full occlusion due to one patch and * that as such no further shaft culling is necessary. */ int ShaftPatchTest(PATCH *patch, SHAFT *shaft) { int i, j, someout, inall[PATCHMAXVERTICES]; SHAFTPLANE *plane; double tmin[PATCHMAXVERTICES], tmax[PATCHMAXVERTICES], ptol[PATCHMAXVERTICES]; RAY ray; float dist; HITREC hitstore; /* start by assuming that all vertices are on the negative side ("inside") * all shaft planes. */ someout = FALSE; for (j=0; jnrvertices; j++) { inall[j] = TRUE; tmin[j] = 0.; /* defines the segment of the edge that lays within the shaft */ tmax[j] = 1.; ptol[j] = VECTORTOLERANCE(*patch->vertex[j]->point); /* vertex tolerance */ } for (i=0, plane=&shaft->plane[0]; iplanes; i++, plane++) { /* test patch against i-th plane of the shaft */ DVECTOR plane_normal; double e[PATCHMAXVERTICES], tolerance; int in, out, side[PATCHMAXVERTICES]; VECTORSET(plane_normal, plane->n[0], plane->n[1], plane->n[2]); in = out = FALSE; for (j=0; jnrvertices; j++) { e[j] = VECTORDOTPRODUCT(plane_normal, *patch->vertex[j]->point) + plane->d; tolerance = fabs(plane->d) * EPSILON + ptol[j]; side[j] = COPLANAR; if (e[j] > tolerance) { side[j] = OUTSIDE; out = TRUE; } else if (e[j] < -tolerance) { side[j] = INSIDE; in = TRUE; } if (side[j] != INSIDE) inall[j] = FALSE; } if (!in) /* patch contains no vertices on the inside of the plane */ return OUTSIDE; if (out) { /* patch contains at least one vertex inside and one */ someout = TRUE; /* outside the plane. A point is inside the shaft * if it is inside *all* planes, but it is outside * as soon it is outside *one* plane. */ for (j=0; jnrvertices; j++) { /* reduce segment of edge that can lay within the shaft. */ int k = (j+1)%patch->nrvertices; if (side[j] != side[k]) { if (side[k] == OUTSIDE) { /* decrease tmax[j] */ if (side[j] == INSIDE) { /* find intersection */ if (tmax[j] > tmin[j]) { double t = e[j] / (e[j] - e[k]); if (t < tmax[j]) tmax[j] = t; } } else /* if (side[j] == COPLANAR) */ { /* whole edge lays outside */ tmax[j] = -EPSILON; } } else if (side[j] == OUTSIDE) { /* increase tmin[j] */ if (side[k] == INSIDE) { /* find intersection */ if (tmin[j] < tmax[j]) { double t = e[j] / (e[j] - e[k]); if (t > tmin[j]) tmin[j] = t; } } else /* if (side[k] == COPLANAR) */ { /* whole edge lays outside */ tmin[j] = 1.+EPSILON; } } } else if (side[j] == OUTSIDE) { /* whole edge lays outside */ tmax[j] = -EPSILON; } } } } /* The remaining tests only work if the shaft planes alone determine the * shaft. */ if (shaft->ref1 || shaft->ref2) return OVERLAP; if (!someout) /* no patch vertices are outside the shaft */ return INSIDE; for (j=0; jnrvertices; j++) if (inall[j]) /* at least one patch vertex is really inside the shaft and */ return OVERLAP; /* at least one vertex is really outside => overlap. */ /* All vertices are outside or on the shaft. Check whether there are edges * intersecting the shaft. */ for (j=0; jnrvertices; j++) if (tmin[j] + EPSILON < tmax[j] - EPSILON) return OVERLAP; /* All vertices and edges of the patch are outside the shaft. Either the * patch is totally outside the shaft, either, the patch cuts the shaft. * If the line segment connecting the midpoints of the polygons defining * the shaft intersects the patch, the patch cuts the shaft. If not, * the patch lays fully outside. */ ray.pos = shaft->center1; VECTORSUBTRACT(shaft->center2, shaft->center1, ray.dir); dist = 1. - EPSILON; if (PatchIntersect(patch, &ray, EPSILON, &dist, HIT_FRONT|HIT_BACK, &hitstore)) { shaft->cut = TRUE; return OVERLAP; } return OUTSIDE; } /* returns TRUE if the geomerty is not to be enclosed in the shaft */ static int Omit(SHAFT *shaft, GEOM *geom) { int i; for (i=0; inromit; i++) if (shaft->omit[i] == geom) return TRUE; return FALSE; } /* returns TRUE if the geomerty is not to be opened during shaft culling */ static int DontOpen(SHAFT *shaft, GEOM *geom) { int i; for (i=0; inrdontopen; i++) if (shaft->dontopen[i] == geom) return TRUE; return FALSE; } /* Given a PATCHLIST pl and a shaft. This routine will check every patch in pl to * see if it is inside, outside or overlapping the shaft. Inside or overlapping patches * are added to culledpatchlist. A pointer to the possibly enlonged culledpatchlist * is returned. */ PATCHLIST *ShaftCullPatchlist(PATCHLIST *pl, SHAFT *shaft, PATCHLIST *culledpatchlist) { int total, kept, bbside; /* can be used for ratio-open strategies */ for (kept=total=0; pl && !shaft->cut; pl=pl->next, total++) { if (pl->patch->omit || Omit(shaft, (GEOM *)pl->patch)) continue; if (!pl->patch->bounds) { /* compute bounds */ BOUNDINGBOX bounds; PatchBounds(pl->patch, bounds); } if ((bbside=ShaftBoxTest(pl->patch->bounds, shaft))!=OUTSIDE) { /* patch bounding box is inside the shaft, or overlaps with it. If it * overlaps, do a more expensive, but definitive, test to see whether * the patch itself is inside, outside or overlapping the shaft. */ if (bbside==INSIDE || ShaftPatchTest(pl->patch, shaft)!=OUTSIDE) { culledpatchlist = PatchListAdd(culledpatchlist, pl->patch); kept++; } } } return culledpatchlist; } /* Adds the geom to the candlist, possibly duplicating it if the geom * was created during previous shaftculling. */ static GEOMLIST *Keep(GEOM *geom, GEOMLIST *candlist) { if (geom->omit) return candlist; if (geom->shaftcullgeom) { GEOM *newgeom = GeomDuplicate(geom); newgeom->shaftcullgeom = TRUE; candlist = GeomListAdd(candlist, newgeom); } else { candlist = GeomListAdd(candlist, geom); } return candlist; } /* Breaks the geom into it's components and does shaft culling on * the components. */ static GEOMLIST *Open(GEOM *geom, SHAFT *shaft, GEOMLIST *candlist) { if (geom->omit) return candlist; if (GeomIsAggregate(geom)) { candlist = DoShaftCulling(GeomPrimList(geom), shaft, candlist); } else { PATCHLIST *patchlist; patchlist = GeomPatchList(geom); patchlist = ShaftCullPatchlist(patchlist, shaft, NULL); if (patchlist) { GEOM *newgeom; newgeom = GeomCreate(patchlist, PatchListMethods()); newgeom->shaftcullgeom = TRUE; candlist = GeomListAdd(candlist, newgeom); } } return candlist; } /* Tests the geom w.r.t. the shaft: if the geom is inside or overlaps * the shaft, it is copied to the shaft or broken open depending on * the current shaft culling strategy. */ GEOMLIST *ShaftCullGeom(GEOM *geom, SHAFT *shaft, GEOMLIST *candlist) { if (geom->omit || Omit(shaft, geom)) return candlist; /* unbounded geoms always overlap the shaft */ switch (geom->bounded ? ShaftBoxTest(geom->bounds, shaft) : OVERLAP) { case INSIDE: if (strategy == ALWAYS_OPEN && !DontOpen(shaft, geom)) candlist = Open(geom, shaft, candlist); else candlist = Keep(geom, candlist); break; case OVERLAP: if (strategy == KEEP_CLOSED || DontOpen(shaft, geom)) candlist = Keep(geom, candlist); else candlist = Open(geom, shaft, candlist); break; default: break; } return candlist; } /* adds all objects from world that overlap or lay inside the shaft to * candlist, returns the new candidate list */ /* During shaftculling patchlist "geoms" are created - they (and only they) * need to be destroyed when destroying a geom candidate list created by * DoShaftCulling - for other kinds of geoms, only a pointer is copied */ GEOMLIST *DoShaftCulling(GEOMLIST *world, SHAFT *shaft, GEOMLIST *candlist) { for (; world && !shaft->cut; world=world->next) { candlist = ShaftCullGeom(world->geom, shaft, candlist); } return candlist; } void FreeCandidateList(GEOMLIST *candlist) { GEOMLIST *gl; /* only destroy geoms that were generated for shaft culling. */ for (gl=candlist; gl; gl=gl->next) if (gl->geom->shaftcullgeom) GeomDestroy(gl->geom); GeomListDestroy(candlist); }