home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Resource Library: Graphics
/
graphics-16000.iso
/
msdos
/
raytrace
/
rayshade
/
src
/
hf.c
< prev
next >
Wrap
Text File
|
1992-05-04
|
18KB
|
746 lines
/*
* hf.c
*
* Copyright (C) 1989, 1991, Craig E. Kolb
* All rights reserved.
*
* This software may be freely copied, modified, and redistributed
* provided that this copyright notice is preserved on all copies.
*
* You may not distribute this software, in whole or in part, as part of
* any commercial product without the express consent of the authors.
*
* There is no warranty or other guarantee of fitness of this software
* for any purpose. It is provided solely "as is".
*
* $Id: hf.c,v 4.0.1.1 91/09/29 15:44:53 cek Exp Locker: cek $
*
* $Log: hf.c,v $
* Revision 4.0.1.1 91/09/29 15:44:53 cek
* patch1: Error messages missing newline.
*
* Revision 4.0 91/07/17 14:38:15 kolb
* Initial version.
*
*/
#include "geom.h"
#include "hf.h"
static Methods *iHfMethods = NULL;
static char hfName[] = "heighfield";
static void integrate_grid(), QueueTri();
static int DDA2D(), CheckCell();
static Float intHftri();
static float minalt(), maxalt();
typedef struct {
int stepX, stepY;
Float tDX, tDY;
float minz, maxz;
int outX, outY;
Vector cp, pDX, pDY;
} Trav2D;
hfTri *CreateHfTriangle(), *GetQueuedTri();
unsigned long HFTests, HFHits;
Hf *
HfCreate(filename)
char *filename;
{
Hf *hf;
FILE *fp;
float val, *maxptr, *minptr;
int i, j;
#if defined(__WATCOMC__) || defined(__BORLANDC__)
fp = fopen(filename, "rb");
#else
fp = fopen(filename, "r");
#endif
if (fp == (FILE *)NULL) {
RLerror(RL_ABORT, "Cannot open heightfield file \"%s\".\n",
filename);
return (Hf *)NULL;
}
hf = (Hf *)RayMalloc(sizeof(Hf));
/*
* Make the following an option someday.
*/
hf->BestSize = BESTSIZE;
/*
* Store the inverse for faster computation.
*/
hf->iBestSize = 1. / (float)hf->BestSize;
/*
* Get HF size.
*/
if (fread((char *)&hf->size, sizeof(int), 1, fp) == 0) {
RLerror(RL_ABORT, "Cannot read height field size.\n");
return (Hf *)NULL;
}
hf->data = (float **)share_malloc(hf->size * sizeof(float *));
for (i = 0; i < hf->size; i++) {
hf->data[i] = (float *)share_malloc(hf->size * sizeof(float));
/*
* Read in row of HF data.
*/
if (fread((char *)hf->data[i],sizeof(float),hf->size,fp)
!= hf->size) {
RLerror(RL_ABORT, "Not enough heightfield data.\n");
return (Hf *)NULL;
}
for (j = 0; j < hf->size; j++) {
val = hf->data[i][j];
if (val <= HF_UNSET) {
hf->data[i][j] = HF_UNSET;
/*
* Don't include the point in min/max
* calculations.
*/
continue;
}
if (val > hf->maxz)
hf->maxz = val;
if (val < hf->minz)
hf->minz = val;
}
}
(void)fclose(fp);
/*
* Allocate levels of grid. hf->levels = log base BestSize of hf->size
*/
for (i = hf->size, hf->levels = 0; i > hf->BestSize; i /= hf->BestSize,
hf->levels++)
;
hf->levels++;
hf->qsize = CACHESIZE;
hf->q = (hfTri **)Calloc((unsigned)hf->qsize, sizeof(hfTri *));
hf->qtail = 0;
hf->lsize = (int *)share_malloc(hf->levels * sizeof(int));
hf->spacing = (float *)share_malloc(hf->levels * sizeof(float));
hf->boundsmax = (float ***)share_malloc(hf->levels * sizeof(float **));
hf->boundsmin = (float ***)share_malloc(hf->levels * sizeof(float **));
hf->spacing[0] = hf->size -1;
hf->lsize[0] = (int)hf->spacing[0];
hf->boundsmax[0] = (float **)share_malloc(hf->lsize[0]*sizeof(float *));
hf->boundsmin[0] = (float **)share_malloc(hf->lsize[0]*sizeof(float *));
/*
* Compute initial bounding boxes
*/
for (i = 0; i < hf->lsize[0]; i++) {
hf->boundsmax[0][i]=(float *)share_malloc(hf->lsize[0]*sizeof(float));
hf->boundsmin[0][i]=(float *)share_malloc(hf->lsize[0]*sizeof(float));
maxptr = hf->boundsmax[0][i];
minptr = hf->boundsmin[0][i];
for (j = 0; j < hf->lsize[0]; j++) {
*maxptr++ = maxalt(i, j, hf->data) + EPSILON;
*minptr++ = minalt(i, j, hf->data) - EPSILON;
}
}
for (i = 1; i < hf->levels; i++) {
hf->spacing[i] = hf->spacing[i-1] * hf->iBestSize;
hf->lsize[i] = (int)hf->spacing[i];
if ((Float)hf->lsize[i] != hf->spacing[i])
hf->lsize[i]++;
hf->boundsmax[i]=(float **)share_malloc(hf->lsize[i]*sizeof(float *));
hf->boundsmin[i]=(float **)share_malloc(hf->lsize[i]*sizeof(float *));
for (j = 0; j < hf->lsize[i]; j++) {
hf->boundsmax[i][j] = (float *)share_malloc(hf->lsize[i] *
sizeof(float));
hf->boundsmin[i][j] = (float *)share_malloc(hf->lsize[i] *
sizeof(float));
}
integrate_grid(hf, i);
}
hf->boundbox[LOW][X] = hf->boundbox[LOW][Y] = 0;
hf->boundbox[HIGH][X] = hf->boundbox[HIGH][Y] = 1;
hf->boundbox[LOW][Z] = hf->minz;
hf->boundbox[HIGH][Z] = hf->maxz;
return hf;
}
Methods *
HfMethods()
{
if (iHfMethods == (Methods *)NULL) {
iHfMethods = MethodsCreate();
iHfMethods->create = (GeomCreateFunc *)HfCreate;
iHfMethods->methods = HfMethods;
iHfMethods->name = HfName;
iHfMethods->intersect = HfIntersect;
iHfMethods->normal = HfNormal;
iHfMethods->uv = HfUV;
iHfMethods->bounds = HfBounds;
iHfMethods->stats = HfStats;
iHfMethods->checkbounds = TRUE;
iHfMethods->closed = FALSE;
}
return iHfMethods;
}
/*
* Intersect ray with height field.
*/
int
HfIntersect(hf, ray, mindist, maxdist)
Hf *hf;
Ray *ray;
Float mindist, *maxdist;
{
Vector hitpos;
Float offset;
Trav2D trav;
HFTests++;
/*
* Find where we hit the hf cube.
*/
VecAddScaled(ray->pos, mindist, ray->dir, &hitpos);
if (OutOfBounds(&hitpos, hf->boundbox)) {
offset = *maxdist;
if (!BoundsIntersect(ray, hf->boundbox, mindist, &offset))
return FALSE;
hitpos.x = ray->pos.x + ray->dir.x * offset;
hitpos.y = ray->pos.y + ray->dir.y * offset;
hitpos.z = ray->pos.z + ray->dir.z * offset;
} else
hitpos = ray->pos;
/*
* Find out in which cell "hitpoint" is.
*/
if (equal(hitpos.x, 1.))
hitpos.x -= EPSILON;
if (equal(hitpos.y, 1.))
hitpos.y -= EPSILON;
if (ray->dir.x < 0.) {
trav.stepX = trav.outX = -1;
trav.tDX = -1. / (ray->dir.x * hf->spacing[hf->levels -1]);
} else if (ray->dir.x > 0.) {
trav.stepX = 1;
trav.outX = hf->lsize[hf->levels -1];
/*
* (1./size) / ray
*/
trav.tDX = 1. / (ray->dir.x * hf->spacing[hf->levels -1]);
}
if (ray->dir.y < 0.) {
trav.stepY = trav.outY = -1;
trav.tDY = -1. / (ray->dir.y * hf->spacing[hf->levels -1]);
} else if (ray->dir.y > 0.) {
trav.stepY = 1;
trav.outY = hf->lsize[hf->levels -1];
trav.tDY = 1. / (ray->dir.y * hf->spacing[hf->levels -1]);
}
trav.pDX.x = ray->dir.x * trav.tDX;
trav.pDX.y = ray->dir.y * trav.tDX;
trav.pDX.z = ray->dir.z * trav.tDX;
trav.pDY.x = ray->dir.x * trav.tDY;
trav.pDY.y = ray->dir.y * trav.tDY;
trav.pDY.z = ray->dir.z * trav.tDY;
trav.cp = hitpos;
trav.minz = hf->minz;
trav.maxz = hf->maxz;
if (DDA2D(hf, &ray->pos, &ray->dir, hf->levels -1, &trav, maxdist)) {
HFHits++;
return TRUE;
}
return FALSE;
}
/*
* Traverse the grid using a modified DDA algorithm. If the extent of
* the ray over a cell intersects the bounding volume defined by the
* four corners of the cell, either recurse or perform ray/surface
* intersection test.
*/
static int
DDA2D(hf, pos, ray, level, trav, maxdist)
Hf *hf;
Vector *pos, *ray;
int level;
Trav2D *trav;
Float *maxdist;
{
int x, y, size, posZ;
float **boundsmin, **boundsmax, spacing;
Float tX, tY;
Trav2D newtrav;
Vector nxp, nyp;
size = hf->lsize[level];
spacing = hf->spacing[level];
posZ = (ray->z > 0.);
x = trav->cp.x * hf->spacing[level];
if (x == size)
x--;
y = trav->cp.y * hf->spacing[level];
if (y == size)
y--;
boundsmax = hf->boundsmax[level];
boundsmin = hf->boundsmin[level];
if (trav->outX > size) trav->outX = size;
if (trav->outY > size) trav->outY = size;
if (trav->outX < 0) trav->outX = -1;
if (trav->outY < 0) trav->outY = -1;
if (ray->x < 0.) {
tX = (x /spacing - trav->cp.x) / ray->x;
} else if (ray->x > 0.)
tX = ((x+1)/spacing - trav->cp.x) / ray->x;
else
tX = FAR_AWAY;
if (ray->y < 0.) {
tY = (y /spacing - trav->cp.y) / ray->y;
} else if (ray->y > 0.)
tY = ((y+1)/spacing - trav->cp.y) / ray->y;
else
tY = FAR_AWAY;
nxp.x = trav->cp.x + tX * ray->x;
nxp.y = trav->cp.y + tX * ray->y;
nxp.z = trav->cp.z + tX * ray->z;
nyp.x = trav->cp.x + tY * ray->x;
nyp.y = trav->cp.y + tY * ray->y;
nyp.z = trav->cp.z + tY * ray->z;
do {
if (tX < tY) {
if ((posZ && trav->cp.z <= boundsmax[y][x] &&
nxp.z >= boundsmin[y][x]) ||
(!posZ && trav->cp.z >= boundsmin[y][x] &&
nxp.z <= boundsmax[y][x])) {
if (level) {
/*
* Recurse -- compute constants
* needed for next level.
* Nicely enough, this just
* involves a few multiplications.
*/
newtrav = *trav;
newtrav.tDX *= hf->iBestSize;
newtrav.tDY *= hf->iBestSize;
newtrav.maxz = boundsmax[y][x];
newtrav.minz = boundsmin[y][x];
if (ray->x < 0.)
newtrav.outX=hf->BestSize*x-1;
else
newtrav.outX=hf->BestSize*(x+1);
if (ray->y < 0.)
newtrav.outY=hf->BestSize*y-1;
else
newtrav.outY=hf->BestSize*(y+1);
newtrav.pDX.x *= hf->iBestSize;
newtrav.pDX.y *= hf->iBestSize;
newtrav.pDX.z *= hf->iBestSize;
newtrav.pDY.x *= hf->iBestSize;
newtrav.pDY.y *= hf->iBestSize;
newtrav.pDY.z *= hf->iBestSize;
if (DDA2D(hf,pos,ray,level-1,
&newtrav, maxdist))
return TRUE;
} else if (CheckCell(x,y,hf,ray,pos,maxdist))
return TRUE;
}
x += trav->stepX; /* Move in X */
if (*maxdist < tX || x == trav->outX)
/* If outside, quit */
return FALSE;
tX += trav->tDX; /* Update position on ray */
trav->cp = nxp; /* cur pos gets next pos */
nxp.x += trav->pDX.x; /* Compute next pos */
nxp.y += trav->pDX.y;
nxp.z += trav->pDX.z;
} else {
if ((posZ && trav->cp.z <= boundsmax[y][x] &&
nyp.z >= boundsmin[y][x]) ||
(!posZ && trav->cp.z >= boundsmin[y][x] &&
nyp.z <= boundsmax[y][x])) {
if (level) {
/* Recurse */
newtrav = *trav;
newtrav.tDX *= hf->iBestSize;
newtrav.tDY *= hf->iBestSize;
newtrav.maxz = boundsmax[y][x];
newtrav.minz = boundsmin[y][x];
if (ray->x < 0.)
newtrav.outX=hf->BestSize*x-1;
else
newtrav.outX=hf->BestSize*(x+1);
if (ray->y < 0.)
newtrav.outY=hf->BestSize*y-1;
else
newtrav.outY=hf->BestSize*(y+1);
newtrav.pDX.x *= hf->iBestSize;
newtrav.pDX.y *= hf->iBestSize;
newtrav.pDX.z *= hf->iBestSize;
newtrav.pDY.x *= hf->iBestSize;
newtrav.pDY.y *= hf->iBestSize;
newtrav.pDY.z *= hf->iBestSize;
if (DDA2D(hf,pos,ray,level-1,
&newtrav, maxdist))
return TRUE;
} else if (CheckCell(x,y,hf,ray,pos,maxdist))
return TRUE;
}
y += trav->stepY;
if (*maxdist < tY || y == trav->outY)
return FALSE;
tY += trav->tDY;
trav->cp = nyp;
nyp.x += trav->pDY.x;
nyp.y += trav->pDY.y;
nyp.z += trav->pDY.z;
}
} while ((trav->cp.x <= 1. && trav->cp.y <= 1.) &&
((posZ && trav->cp.z <= trav->maxz) ||
(!posZ && trav->cp.z >= trav->minz)));
/*
* while ((we're inside the horizontal bounding box)
* (usually caught by outX & outY, but
* it's possible to go "too far" due to
* the fact that our levels of grids do
* not "nest" exactly if gridsize%BestSize != 0)
* and
* ((if ray->z is positive and we haven't gone through
* the upper bounding plane) or
* (if ray->z is negative and we haven't gone through
* the lower bounding plane)));
*/
return FALSE;
}
/*
* Check for ray/cell intersection
*/
static int
CheckCell(x, y, hf, ray, pos, maxdist)
int x, y;
Hf *hf;
Vector *ray, *pos;
Float *maxdist;
{
hfTri *tri1, *tri2;
Float d1, d2;
d1 = d2 = FAR_AWAY;
if (tri1 = CreateHfTriangle(hf, x, y, x+1, y, x, y+1, TRI1))
d1 = intHftri(ray, pos, tri1);
if (tri2 = CreateHfTriangle(hf, x+1, y, x+1, y+1, x, y+1, TRI2))
d2 = intHftri(ray, pos, tri2);
if (d1 == FAR_AWAY && d2 == FAR_AWAY)
return FALSE;
if (d1 < d2) {
if (d1 < *maxdist) {
hf->hittri = *tri1;
*maxdist = d1;
return TRUE;
}
return FALSE;
}
if (d2 < *maxdist) {
hf->hittri = *tri2;
*maxdist = d2;
return TRUE;
}
return FALSE;
}
static hfTri *
CreateHfTriangle(hf, x1, y1, x2, y2, x3, y3, which)
Hf *hf;
int x1, y1, x2, y2, x3, y3, which;
{
hfTri *tri;
Float xid, yid;
Vector tmp1, tmp2;
/*
* Don't use triangles with "unset" vertices.
*/
if (hf->data[y1][x1] == HF_UNSET ||
hf->data[y2][x2] == HF_UNSET ||
hf->data[y3][x3] == HF_UNSET)
return (hfTri *)0;
xid = (Float)x1 / (Float)(hf->size -1);
yid = (Float)y1 / (Float)(hf->size -1);
if ((tri = GetQueuedTri(hf, xid, yid, which)) != (hfTri *)0)
return tri;
tri = (hfTri *)RayMalloc(sizeof(hfTri));
tri->type = which;
tri->v1.x = xid;
tri->v1.y = yid;
tri->v1.z = hf->data[y1][x1];
tri->v2.x = (Float)x2 / (Float)(hf->size-1);
tri->v2.y = (Float)y2 / (Float)(hf->size-1);
tri->v2.z = hf->data[y2][x2];
tri->v3.x = (Float)x3 / (Float)(hf->size-1);
tri->v3.y = (Float)y3 / (Float)(hf->size-1);
tri->v3.z = hf->data[y3][x3];
tmp1.x = tri->v2.x - tri->v1.x;
tmp1.y = tri->v2.y - tri->v1.y;
tmp1.z = tri->v2.z - tri->v1.z;
tmp2.x = tri->v3.x - tri->v1.x;
tmp2.y = tri->v3.y - tri->v1.y;
tmp2.z = tri->v3.z - tri->v1.z;
(void)VecNormCross(&tmp1, &tmp2, &tri->norm);
tri->d = -dotp(&tri->v1, &tri->norm);
QueueTri(hf, tri);
return tri;
}
/*
* Intersect ray with right isoscoles triangle, the hypotenuse of which
* has slope of -1.
*/
static Float
intHftri(ray, pos, tri)
hfTri *tri;
Vector *pos, *ray;
{
Float u, v, dist, xpos, ypos;
u = dotp(&tri->norm, pos) + tri->d;
v = dotp(&tri->norm, ray);
if ((u <= 0. || v > -EPSILON) && (u >= 0. && v < EPSILON))
return FAR_AWAY;
dist = -u / v;
if (dist < EPSILON)
return FAR_AWAY;
xpos = pos->x + dist*ray->x;
ypos = pos->y + dist*ray->y;
if (tri->type == TRI1 && xpos >= tri->v1.x && ypos >= tri->v1.y &&
xpos + ypos <= tri->v2.x + tri->v2.y)
return dist;
if (tri->type == TRI2 && xpos <= tri->v2.x && ypos <= tri->v2.y &&
xpos + ypos >= tri->v1.x + tri->v1.y)
return dist;
return FAR_AWAY;
}
/*
* Compute normal to height field.
*/
/*ARGSUSED*/
int
HfNormal(hf, pos, nrm, gnrm)
Hf *hf;
Vector *pos, *nrm, *gnrm;
{
*gnrm = *nrm = hf->hittri.norm;
return FALSE;
}
/*ARGSUSED*/
void
HfUV(hf, pos, norm, uv, dpdu, dpdv)
Hf *hf;
Vector *pos, *norm, *dpdu, *dpdv;
Vec2d *uv;
{
uv->u = pos->x;
uv->v = pos->y;
if (dpdu) {
dpdu->x = 1.;
dpdu->y = dpdv->z = 0.;
dpdv->x = dpdv->z = 0.;
dpdv->y = 1.;
}
}
/*
* Compute heightfield bounding box.
*/
void
HfBounds(hf, bounds)
Hf *hf;
Float bounds[2][3];
{
/*
* By default, height fields are centered at (0.5, 0.5, 0.)
*/
bounds[LOW][X] = bounds[LOW][Y] = 0;
bounds[HIGH][X] = bounds[HIGH][Y] = 1;
bounds[LOW][Z] = hf->minz;
bounds[HIGH][Z] = hf->maxz;
}
/*
* Build min/max altitude value arrays for the given grid level.
*/
static void
integrate_grid(hf, level)
Hf *hf;
int level;
{
int i, j, k, l, ii, ji;
float max_alt, min_alt;
float **maxinto, **mininto, **frommax, **frommin, *minptr, *maxptr;
int insize, fromsize, fact;
maxinto = hf->boundsmax[level];
mininto = hf->boundsmin[level];
insize = hf->lsize[level];
frommax = hf->boundsmax[level-1];
frommin = hf->boundsmin[level-1];
fact = hf->BestSize;
fromsize = hf->lsize[level-1];
ii = 0;
for (i = 0; i < insize; i++) {
ji = 0;
for (j = 0; j < insize; j++) {
max_alt = HF_UNSET;
min_alt = -HF_UNSET;
for (k = 0; k <= fact; k++) {
if (ii+k >= fromsize)
continue;
maxptr = &frommax[ii+k][ji];
minptr = &frommin[ii+k][ji];
for (l = 0; l <= fact; l++,maxptr++,minptr++) {
if (ji+l >= fromsize)
continue;
if (*maxptr > max_alt)
max_alt = *maxptr;
if (*minptr < min_alt)
min_alt = *minptr;
}
}
maxinto[i][j] = max_alt + EPSILON;
mininto[i][j] = min_alt - EPSILON;
ji += fact;
}
ii += fact;
}
}
/*
* Place the given triangle in the triangle cache.
*/
static void
QueueTri(hf, tri)
Hf *hf;
hfTri *tri;
{
if (hf->q[hf->qtail]) /* Free old triangle data */
free((voidstar)hf->q[hf->qtail]);
hf->q[hf->qtail] = tri; /* Put on tail */
hf->qtail = (hf->qtail + 1) % hf->qsize; /* Increment tail */
}
/*
* Search list of cached trianges to see if this triangle has been
* cached. If so, return a pointer to it. If not, return null pointer.
*/
static hfTri *
GetQueuedTri(hf, x, y, flag)
Hf *hf;
Float x, y;
int flag;
{
register int i;
register hfTri **tmp;
for (i = 0, tmp = hf->q; i < hf->qsize; i++, tmp++) {
if (*tmp && (*tmp)->v1.x == x && (*tmp)->v1.y == y &&
(*tmp)->type == flag)
return *tmp; /* vertices & flag match, return it */
}
return (hfTri *)0;
}
/*
* Return maximum height of cell indexed by y,x. This could be done
* as a macro, but many C compliers will choke on it.
*/
static float
minalt(y,x,data)
int x, y;
float **data;
{
float min_alt;
min_alt = min(data[y][x], data[y+1][x]);
min_alt = min(min_alt, data[y][x+1]);
min_alt = min(min_alt, data[y+1][x+1]);
return min_alt;
}
/*
* Return maximum cell height, as above.
*/
static float
maxalt(y,x,data)
int x, y;
float **data;
{
float max_alt;
max_alt = max(data[y][x], data[y+1][x]);
max_alt = max(max_alt, data[y][x+1]);
max_alt = max(max_alt, data[y+1][x+1]);
return max_alt;
}
char *
HfName()
{
return hfName;
}
void
HfStats(tests, hits)
unsigned long *tests, *hits;
{
*tests = HFTests;
*hits = HFHits;
}
void
HfMethodRegister(meth)
UserMethodType meth;
{
if (iHfMethods)
iHfMethods->user = meth;
}