/* grid.c: RayTracing acceleration using a uniform grid */ #include "grid.h" #include "gridP.h" #include "pools.h" #include "error.h" #include "hitlist.h" static POOL **gridItemPoolPtr; /* set in EnGrid for the top grid */ #define NEWGRIDITEM(grid) (GRIDITEM *)NewPoolCell(sizeof(GRIDITEM), 0, "grid items", gridItemPoolPtr) /* unused #define DISPOSEGRIDITEM(ptr, grid) Dispose((char *)ptr, gridItemPool) */ /* *********************************************************************** */ /* voxel grid construction */ static int IsSmall(float *bounds, GRID *grid) { return ((bounds[MAX_X] - bounds[MIN_X]) <= grid->voxsize.x && (bounds[MAX_Y] - bounds[MIN_Y]) <= grid->voxsize.y && (bounds[MAX_Z] - bounds[MIN_Z]) <= grid->voxsize.z); } static GRIDITEM *NewGridItem(void *ptr, unsigned flags, GRID *grid) { GRIDITEM *item = NEWGRIDITEM(grid); item->ptr = ptr; item->flags = flags; return item; } static void EngridItem(GRIDITEM *item, float *itembounds, GRID *grid) { short mina, minb, minc, maxa, maxb, maxc, a, b, c; /* enlarge the bounds by a small amount in all directions */ BOUNDINGBOX bounds; float xext = (grid->bounds[MAX_X] - grid->bounds[MIN_X]) * 1e-4, yext = (grid->bounds[MAX_Y] - grid->bounds[MIN_Y]) * 1e-4, zext = (grid->bounds[MAX_Z] - grid->bounds[MIN_Z]) * 1e-4; BoundsCopy(itembounds, bounds); bounds[MIN_X] -= xext; bounds[MAX_X] += xext; bounds[MIN_Y] -= yext; bounds[MAX_Y] += yext; bounds[MIN_Z] -= zext; bounds[MAX_Z] += zext; mina = x2voxel(bounds[MIN_X],grid); if (mina >= grid->xsize) mina = grid->xsize-1; if (mina < 0) mina = 0; maxa = x2voxel(bounds[MAX_X],grid); if (maxa >= grid->xsize) maxa = grid->xsize-1; if (maxa < 0) maxa = 0; minb = y2voxel(bounds[MIN_Y],grid); if (minb >= grid->ysize) minb = grid->ysize-1; if (minb < 0) minb = 0; maxb = y2voxel(bounds[MAX_Y],grid); if (maxb >= grid->ysize) maxb = grid->ysize-1; if (maxb < 0) maxb = 0; minc = z2voxel(bounds[MIN_Z],grid); if (minc >= grid->zsize) minc = grid->zsize-1; if (minc < 0) minc = 0; maxc = z2voxel(bounds[MAX_Z],grid); if (maxc >= grid->zsize) maxc = grid->zsize-1; if (maxc < 0) maxc = 0; for (a=mina; a<=maxa; a++) for (b=minb; b<=maxb; b++) for (c=minc; c<=maxc; c++) { GRIDITEMLIST **items = &grid->items[CELLADDR(grid, a,b,c)]; (*items) = GridItemListAdd(*items, item); } } static void EngridPatch(PATCH *patch, GRID *grid) { BOUNDINGBOX bounds; EngridItem(NewGridItem(patch, ISPATCH, grid), patch->bounds ? patch->bounds : PatchBounds(patch, bounds), grid); } static void EngridGeom(GEOM *geom, GRID *grid) { if (IsSmall(geom->bounds, grid)) { if (geom->tmp.i /*item count*/ < 10) EngridItem(NewGridItem(geom, ISGEOM, grid), geom->bounds, grid); else { GRID *subgrid = CreateGrid(geom); EngridItem(NewGridItem(subgrid, ISGRID, grid), subgrid->bounds, grid); } } else { if (GeomIsAggregate(geom)) { ForAllGeoms(child, GeomPrimList(geom)) { EngridGeom(child, grid); } EndForAll; } else { ForAllPatches(patch, GeomPatchList(geom)) { EngridPatch(patch, grid); } EndForAll; } } } static GRID *Engrid(GEOM *geom, short na, short nb, short nc, int istop) { GRID *grid; int i; float xext, yext, zext; if (!geom) return (GRID *)NULL; if (!geom->bounds) { Error("Engrid", "Can't engrid an unbounded geom"); return (GRID *)NULL; } if (na <= 0 || nb <= 0 || nc <= 0) { Error("Engrid", "Invalid grid dimensions"); return (GRID *)NULL; } grid = (GRID *)Alloc(sizeof(GRID)); if (istop) /* save all grid items in 1 pool kept with the toplevel grid */ gridItemPoolPtr = &(grid->gridItemPool); /* enlarge the bounds by a small amount */ xext = (geom->bounds[MAX_X] - geom->bounds[MIN_X]) * 1e-4; yext = (geom->bounds[MAX_Y] - geom->bounds[MIN_Y]) * 1e-4; zext = (geom->bounds[MAX_Z] - geom->bounds[MIN_Z]) * 1e-4; BoundsCopy(geom->bounds, grid->bounds); grid->bounds[MIN_X] -= xext; grid->bounds[MAX_X] += xext; grid->bounds[MIN_Y] -= yext; grid->bounds[MAX_Y] += yext; grid->bounds[MIN_Z] -= zext; grid->bounds[MAX_Z] += zext; grid->xsize = na; grid->ysize = nb; grid->zsize = nc; grid->voxsize.x = (grid->bounds[MAX_X] - grid->bounds[MIN_X]) / (float)na; grid->voxsize.y = (grid->bounds[MAX_Y] - grid->bounds[MIN_Y]) / (float)nb; grid->voxsize.z = (grid->bounds[MAX_Z] - grid->bounds[MIN_Z]) / (float)nc; grid->items = (GRIDITEMLIST **)Alloc(na * nb * nc * sizeof(GRIDITEMLIST *)); grid->gridItemPool = (POOL *)NULL; for (i=0; iitems[i] = GridItemListCreate(); EngridGeom(geom, grid); grid->engridded_geom = geom; return grid; } static int GeomCountItems(GEOM *geom) { int count = 0; if (GeomIsAggregate(geom)) { GEOMLIST *primlist = GeomPrimList(geom); ForAllGeoms(child, primlist) { count += GeomCountItems(child); } EndForAll; } else { PATCHLIST *patches = GeomPatchList(geom); count = PatchListCount(patches); } return geom->tmp.i = count; } /* Constructs a recursive grid structure containing the whole geom. */ GRID *CreateGrid(GEOM *geom) { static int level = 0; int gridsize; GRID *grid; if (level == 0) GeomCountItems(geom); gridsize = pow((double)geom->tmp.i /*item count*/, 0.33333) + 1; fprintf(stderr, "Engridding %d items in %d^3 cells level %d voxel grid ... \n", geom->tmp.i, gridsize, level); level++; grid = Engrid(geom, gridsize, gridsize, gridsize, (level==1) ? TRUE : FALSE); level--; return grid; } static void DestroyGridRecursive(GRID *grid) { int i; if (!grid) return; for (i=0; ixsize * grid->ysize * grid->zsize; i++) { ForAllGridItems(item, grid->items[i]) { if (IsGrid(item) && item->ptr) { /* destroy the subgrid */ DestroyGridRecursive(item->ptr); item->ptr = NULL; } } EndForAll; GridItemListDestroy(grid->items[i]); } Free((char *)grid, sizeof(GRID)); } void DestroyGrid(GRID *grid) { if (!grid) return; DestroyGridRecursive(grid); DisposeAll(&grid->gridItemPool); /* all items are kept in a single pool at the top */ } /* ************************************************************************* */ /* ray-grid intersection: Snyder&Barr, SIGGRAPH'87, p123, with several * optimisations/enhancements from rayshade 4.0.6 by Graig Kolb, Stanford U. */ static int RandomRayId(void) { static int count = 0; count++; return (count & RAYCOUNT_MASK); } #ifdef IDEBUG extern int idebug; #endif /* Compute t0, ray's minimal intersection with the whole grid and * position P of this intersection. Returns TRUE if the grid bounds are * intersected and FALSE if the ray passes along the voxel grid. */ static int GridBoundsIntersect(/*IN*/ GRID *grid, RAY *ray, float mindist, float maxdist, /*OUT*/ float *t0, VECTOR *P) { *t0 = mindist; VECTORADDSCALED(ray->pos, *t0, ray->dir, *P); if (OutOfBounds(P, grid->bounds)) { #ifdef IDEBUG if (idebug) { fprintf(stderr, "%s %d: ray org is out of grid bounds\n", __FILE__, __LINE__); } #endif *t0 = maxdist; if (!BoundsIntersect(ray, grid->bounds, mindist, t0)) { #ifdef IDEBUG if (idebug) { fprintf(stderr, "%s %d: ray doesn't intersect grid bounds\n", __FILE__, __LINE__); } #endif return FALSE; } VECTORADDSCALED(ray->pos, *t0, ray->dir, *P); } else { #ifdef IDEBUG if (idebug) { fprintf(stderr, "%s %d: ray org is inside grid bounds\n", __FILE__, __LINE__); } #endif } #ifdef IDEBUG if (idebug) { fprintf(stderr, "%s %d: ray intersects grid bounds at t=%f, P=%f,%f,%f\n", __FILE__, __LINE__, *t0, P->x, P->y, P->z); } #endif return TRUE; } /* initializes grid tracing */ static void GridTraceSetup(/*IN*/ GRID *grid, RAY *ray, float t0, VECTOR *P, /*OUT*/ int *g, VECTOR *tDelta, VECTOR *tNext, int *step, int *out) { /* Compute the grid cell g where this intersection occurs */ g[X] = x2voxel(P->x, grid); if (g[X] >= grid->xsize) g[X] = grid->xsize-1; g[Y] = y2voxel(P->y, grid); if (g[Y] >= grid->ysize) g[Y] = grid->ysize-1; g[Z] = z2voxel(P->z, grid); if (g[Z] >= grid->zsize) g[Z] = grid->zsize-1; /* Setup X: */ /* tDelta->x is the distance increment along the ray to the adjacent * voxel in X direction. * tNext->x is the total distance from the ray origin to the next voxel * in X direction. * step[X] is either +1 or -1 accroding to the ray X direction. * out[X] is -1 or grid->xsize: the first x grid cell index outside the * grid. */ if (fabs(ray->dir.x) > EPSILON) { if (ray->dir.x > 0.) { tDelta->x = grid->voxsize.x / ray->dir.x; tNext->x = t0 + (voxel2x(g[X]+1,grid) - P->x) / ray->dir.x; step[X] = 1; out[X] = grid->xsize; } else { tDelta->x = grid->voxsize.x / -ray->dir.x; tNext->x = t0 + (voxel2x(g[X],grid) - P->x) / ray->dir.x; step[X] = out[X] = -1; } } else { tDelta->x = 0.; tNext->x = HUGE; } /* Setup Y: */ if (fabs(ray->dir.y) > EPSILON) { if (ray->dir.y > 0.) { tDelta->y = grid->voxsize.y / ray->dir.y; tNext->y = t0 + (voxel2y(g[Y]+1,grid) - P->y) / ray->dir.y; step[Y] = 1; out[Y] = grid->ysize; } else { tDelta->y = grid->voxsize.y / -ray->dir.y; tNext->y = t0 + (voxel2y(g[Y],grid) - P->y) / ray->dir.y; step[Y] = out[Y] = -1; } } else { tDelta->y = 0.; tNext->y = HUGE; } /* Setup Z: */ if (fabs(ray->dir.z) > EPSILON) { if (ray->dir.z > 0.) { tDelta->z = grid->voxsize.z / ray->dir.z; tNext->z = t0 + (voxel2z(g[Z]+1,grid) - P->z) / ray->dir.z; step[Z] = 1; out[Z] = grid->zsize; } else { tDelta->z = grid->voxsize.z / -ray->dir.z; tNext->z = t0 + (voxel2z(g[Z],grid) - P->z) / ray->dir.z; step[Z] = out[Z] = -1; } } else { tDelta->z = 0.; tNext->z = HUGE; } } /* Advances to the next grid cell. Assumes setup with GridTraceSetup(). * returns FALSE if the current voxel was the last voxel in the grid intersected * by the ray */ static int NextVoxel(float *t0, int *g, VECTOR *tNext, VECTOR *tDelta, int *step, int *out) { int ingrid = TRUE; if (tNext->x <= tNext->y && tNext->x <= tNext->z) { /* tNext->x is smallest */ g[X] += step[X]; *t0 = tNext->x; tNext->x += tDelta->x; ingrid = g[X] - out[X]; /* FALSE if g[X]==out[X] */ } else if (tNext->y <= tNext->z) { /* tNext->y is smallest */ g[Y] += step[Y]; *t0 = tNext->y; tNext->y += tDelta->y; ingrid = g[Y] - out[Y]; } else { /* tNext->z is smallest */ g[Z] += step[Z]; *t0 = tNext->z; tNext->z += tDelta->z; ingrid = g[Z] - out[Z]; } return ingrid; } /* finds the nearest intersection of the ray with an item (GEOM or PATCH) in * a voxel's item list. If there is an intersection, maxdist will contain * the distance to the intersection point measured from the ray origin * as usual. If there is no intersection, maxdist remains unmodified. */ static HITREC *VoxelIntersect(GRIDITEMLIST *items, RAY *ray, int counter, float mindist, float *maxdist, int hitflags, HITREC *hitstore) { HITREC *hit = NULL; ForAllGridItems(item, items) { if (LastRayId(item) != counter) { /* avoid testing objects multiple times */ HITREC *h = (HITREC *)NULL; if (IsPatch(item)) h = PatchIntersect((PATCH *)item->ptr, ray, mindist, maxdist, hitflags, hitstore); else if (IsGeom(item)) { h = GeomDiscretisationIntersect((GEOM *)item->ptr, ray, mindist, maxdist, hitflags, hitstore); #ifdef IDEBUG if (idebug) { GEOM *geom = (GEOM *)item->ptr; fprintf(stderr, "====> %s %d: Test with geom with bounds as follows %s:\n", __FILE__, __LINE__, !h ? "failed" : "succeeded"); BoundsPrint(stderr, geom->bounds); } #endif } else if (IsGrid(item)) { h = GridIntersect((GRID *)item->ptr, ray, mindist, maxdist, hitflags, hitstore); #ifdef IDEBUG if (idebug) { GRID *grid = (GRID *)item->ptr; fprintf(stderr, "====> %s %d: Test with grid with bounds as follows %s:\n", __FILE__, __LINE__, !h ? "failed" : "succeeded"); BoundsPrint(stderr, grid->bounds); } #endif } if (h) hit = h; UpdateRayId(item, counter); } else { #ifdef IDEBUG if (idebug) { fprintf(stderr, "====> %s %d: omitting test with ", __FILE__, __LINE__); if (IsPatch(item)) { fprintf(stderr, "patch id %d\n", ((PATCH *)item->ptr)->id); } else { fprintf(stderr, "geom or grid\n"); } } #endif } } EndForAll; return hit; } /* traces a ray through a voxel grid. Returns nearest intersection or NULL */ struct HITREC *GridIntersect(GRID *grid, RAY *ray, float mindist, float *maxdist, int hitflags, HITREC *hitstore) { VECTOR tNext, tDelta, P; int step[3], out[3], g[3]; HITREC *hit = NULL; float t0; int counter; if (!grid || !GridBoundsIntersect(grid, ray, mindist, *maxdist, &t0, &P)) { #ifdef IDEBUG if (idebug) { fprintf(stderr, "%s %d: No grid or grid bounds not intersected\n", __FILE__, __LINE__); } #endif return (HITREC *)NULL; } GridTraceSetup(grid, ray, t0, &P, g, &tDelta, &tNext, step, out); /* ray counter in order to avoid testing objects spanning several * voxel grid cells multiple times. */ counter = RandomRayId(); do { GRIDITEMLIST *list = grid->items[CELLADDR(grid, g[X],g[Y],g[Z])]; #ifdef IDEBUG if (idebug) { VECTOR b; fprintf(stderr, "=====> %s %d: grid cell %d,%d,%d, bounds = \n", __FILE__, __LINE__, g[X], g[Y], g[Z]); fprintf(stderr, " %f->%f, %f->%f, %f->%f\n", voxel2x(g[X], grid), voxel2x(g[X]+1, grid), voxel2y(g[Y], grid), voxel2y(g[Y]+1, grid), voxel2z(g[Z], grid), voxel2z(g[Z]+1, grid)); VECTORADDSCALED(ray->pos, t0, ray->dir, b); fprintf(stderr, " t0 = %f, current position = %f,%f,%f\n", t0, b.x, b.y, b.z); fprintf(stderr, " grid cel contents:\n"); ForAllGridItems(item, list) { if (IsPatch(item)) { fprintf(stderr, "patch id %d", ((PATCH *)item->ptr)->id); } else fprintf(stderr, "geom or grid"); fprintf(stderr, " (%s tested), ", (LastRayId(item) == counter) ? "already" : "to be"); } EndForAll; fprintf(stderr, "\n"); } #endif if (list) { HITREC *h; if ((h=VoxelIntersect(list,ray,counter,t0,maxdist,hitflags,hitstore))) hit = h; } #ifdef IDEBUG if (idebug) { VECTOR b; fprintf(stderr, "=====> %s %d: next cell boundaries:\n", __FILE__, __LINE__); VECTORADDSCALED(ray->pos, tNext.x, ray->dir, b); fprintf(stderr, " X: t=%f: %f,%f,%f ", tNext.x, b.x, b.y, b.z); fprintf(stderr, "next cell %d,%d,%d\n", g[X] + step[X], g[Y], g[Z]); VECTORADDSCALED(ray->pos, tNext.y, ray->dir, b); fprintf(stderr, " Y: t=%f: %f,%f,%f ", tNext.y, b.x, b.y, b.z); fprintf(stderr, "next cell %d,%d,%d\n", g[X], g[Y] + step[Y], g[Z]); VECTORADDSCALED(ray->pos, tNext.z, ray->dir, b); fprintf(stderr, " Z: t=%f: %f,%f,%f ", tNext.z, b.x, b.y, b.z); fprintf(stderr, "next cell %d,%d,%d\n", g[X], g[Y], g[Z] + step[Z]); } #endif } while (NextVoxel(&t0, g, &tNext, &tDelta, step, out) && t0 <= *maxdist); return hit; } /* Prepends all hits in the voxel to the hitlist. The modified hitlist * is returned. */ static HITLIST *AllVoxelIntersections(HITLIST *hitlist, GRIDITEMLIST *items, RAY *ray, int counter, float mindist, float maxdist, int hitflags) { HITREC hitstore; ForAllGridItems(item, items) { /* avoid testing objects multiple times */ if (LastRayId(item) != counter) { if (IsPatch(item)) { float tmax = maxdist; HITREC *h = PatchIntersect((PATCH *)item->ptr, ray, mindist, &tmax, hitflags, &hitstore); if (h) { hitlist = HitListAdd(hitlist, DuplicateHit(h)); } } else if (IsGeom(item)) { hitlist = GeomAllDiscretisationIntersections(hitlist, (GEOM *)item->ptr, ray, mindist, maxdist, hitflags); } else if (IsGrid(item)) { hitlist = AllGridIntersections(hitlist, (GRID *)item->ptr, ray, mindist, maxdist, hitflags); } UpdateRayId(item, counter); } } EndForAll; return hitlist; } /* Traces a ray through a voxel grid. Adds all found intersections to the * passed 'hitlist' */ HITLIST *AllGridIntersections(HITLIST *hits, GRID *grid, RAY *ray, float mindist, float maxdist, int hitflags) { VECTOR tNext, tDelta, P; int step[3], out[3], g[3]; float t0; int counter; if (!grid || !GridBoundsIntersect(grid, ray, mindist, maxdist, &t0, &P)) return hits; GridTraceSetup(grid, ray, t0, &P, g, &tDelta, &tNext, step, out); /* ray counter in order to avoid testing objects spanning several * voxel grid cells multiple times. */ counter = RandomRayId(); do { GRIDITEMLIST *itemlist = grid->items[CELLADDR(grid, g[X],g[Y],g[Z])]; if (itemlist) hits = AllVoxelIntersections(hits,itemlist,ray,counter,t0,maxdist,hitflags); } while (NextVoxel(&t0, g, &tNext, &tDelta, step, out) && t0 <= maxdist); return hits; }