/***** implicit.h */ /* header file for implicit surface polygonizer, implicit.c */ #ifndef IMPLICIT_HDR #define IMPLICIT_HDR typedef struct { /* a three-dimensional point */ float x, y, z; /* its coordinates */ } jbPOINT; typedef struct { /* surface vertex */ jbPOINT position, normal; /* position and surface normal */ } VERTEX; typedef struct { /* list of vertices in polygonization */ int count, max; /* # vertices, max # allowed */ VERTEX *ptr; /* dynamically allocated */ } VERTICES; #ifdef __cplusplus extern "C" { #endif char *polygonize ( float (*function)(float x, float y, float z), float size, int bounds, float x, float y, float z, int (*triproc)(int i1, int i2, int i3, VERTICES vertices)); /* see implicit.c for explanation of arguments */ #ifdef __cplusplus } #endif #endif /***** implicit.c */ /* * ANSI C code from the article * "An Implicit Surface Polygonizer" * by Jules Bloomenthal * in "Graphics Gems IV", Academic Press, 1994 * * implicit.c * an implicit surface polygonizer, translated from Mesa to ANSI C * applications should call polygonize() * * to compile a test program for ASCII output: * cc implicit.c -o implicit -lm * * to compile a test program for display using OpenGL and GLUT: * cc -DUSEGLUT implicit.c -o implicit -lgl_s -lm * to compile for subroutine use without main: * cc -DNOMAIN -c implicit.c * * Authored by Jules Bloomenthal, Xerox PARC. * Copyright (c) Xerox Corporation, 1991. All rights reserved. * Permission is granted to reproduce, use and distribute this code for * any and all purposes, provided that this notice appears in all copies * * Last modified 11 Jan 95 by Jules Bloomenthal and Paul Heckbert */ #include #include #include #include /* #include "implicit.h" */ #define RES 10 /* # converge iterations */ #define L 0 /* left direction: -x, -i */ #define R 1 /* right direction: +x, +i */ #define B 2 /* bottom direction: -y, -j */ #define T 3 /* top direction: +y, +j */ #define jbN 4 /* near direction: -z, -k */ #define F 5 /* far direction: +z, +k */ #define LBN 0 /* left bottom near corner */ #define LBF 1 /* left bottom far corner */ #define LTN 2 /* left top near corner */ #define LTF 3 /* left top far corner */ #define RBN 4 /* right bottom near corner */ #define RBF 5 /* right bottom far corner */ #define RTN 6 /* right top near corner */ #define RTF 7 /* right top far corner */ /* the LBN corner of cube (i, j, k), corresponds with location * (start.x+(i-.5)*size, start.y+(j-.5)*size, start.z+(k-.5)*size) */ #define RAND() ((rand()&32767)/32767.) /* random number, 0--1 */ #define HASHBIT (5) #define HSIZE (size_t)(1<<(3*HASHBIT)) /* hash table size (32768) */ #define MASK ((1<>(bit))&1) #define FLIP(i,bit) ((i)^1<<(bit)) /* flip the given bit of i */ typedef struct { /* test the function for a signed value */ jbPOINT p; /* location of test */ float value; /* function value at p */ int ok; /* if value is of correct sign */ } TEST; typedef struct cornerlist { /* list of corners */ int i, j, k; /* corner id */ float value; /* corner value */ struct cornerlist *next; /* remaining elements */ } CORNERLIST; typedef struct { /* partitioning cell (cube) */ int i, j, k; /* lattice location of cube */ CORNERLIST *corners[8]; /* eight corners */ } CUBE; typedef struct cubes { /* linked list (stack) of cubes */ CUBE cube; /* a single cube */ struct cubes *next; /* remaining elements */ } CUBES; typedef struct centerlist { /* list of cube locations */ int i, j, k; /* cube location */ struct centerlist *next; /* remaining elements */ } CENTERLIST; typedef struct edgelist { /* list of edges */ int i1, j1, k1, i2, j2, k2; /* edge corner ids */ int vid; /* vertex id */ struct edgelist *next; /* remaining elements */ } EDGELIST; typedef struct intlist { /* list of integers */ int i; /* an integer */ struct intlist *next; /* remaining elements */ } INTLIST; typedef struct intlists { /* list of list of integers */ INTLIST *list; /* a list of integers */ struct intlists *next; /* remaining elements */ } INTLISTS; typedef struct process { /* parameters, function, storage */ float (*function) (float x, float y, float z); /* implicit surface function */ int (*triproc)(int i1, int i2, int i3, VERTICES vertices); /* triangle output function */ float size, delta; /* cube size, normal delta */ int bounds; /* cube range within lattice */ jbPOINT start; /* start point on surface */ CUBES *cubes; /* active cubes */ VERTICES vertices; /* surface vertices */ CENTERLIST **centers; /* cube center hash table */ CORNERLIST **corners; /* corner value hash table */ EDGELIST **edges; /* edge and vertex id hash table */ } PROCESS; char *mycalloc (int nitems, int nbytes); CORNERLIST *setcorner (PROCESS *p, int i, int j, int k); int converge ( jbPOINT *p1, jbPOINT *p2, float v, float (*function)(float x, float y, float z), jbPOINT *p); TEST find (int sign, PROCESS *p, float x, float y, float z); #ifndef NOMAIN /**** A Test Program ****/ /* sphere: an inverse square function (always positive) */ float sphere (float x, float y, float z) { float rsq = x*x+y*y+z*z; return 1.0/(rsq < 0.00001? 0.00001 : rsq); } /* blob: a three-pole blend function, try size = .1 */ float blob (float x, float y, float z) { return 4.-sphere(x+.5,y-.5,z-.5) -sphere(x-.5,y+.5,z-.5) -sphere(x-.5,y-.5,z+.5); } #ifdef USEGLUT /**************************************************************/ #include int shadepolys = 1, nvertices = 0, ntriangles = 0; /* triangle: called by polygonize() for each triangle; set SGI lines */ int triangle (int i1, int i2, int i3, VERTICES vertices) { int i, ids[3]; ids[0] = i3; ids[1] = i2; ids[2] = i1; glBegin(shadepolys ? GL_POLYGON : GL_LINE_LOOP); for (i = 0; i < 3; i++) { VERTEX *vv = &vertices.ptr[ids[i]]; if (shadepolys) glNormal3f(vv->normal.x,vv->normal.y,vv->normal.z); glVertex3f(vv->position.x,vv->position.y,vv->position.z); } glEnd(); ntriangles++; nvertices = vertices.count; return 1; } GLuint thesurf; void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glRotatef(1.0, 0.0,1.0,0.0); glCallList(thesurf); glutSwapBuffers(); printf("display\n"); } void idle() { glutPostRedisplay(); } /* main: call polygonize() with blob function, display on SGI */ main (int ac, char *av[]) { char *err; int sp = shadepolys = ac < 2 || strcmp(av[1], "-lines"); float size = shadepolys? 0.2 : 0.1; glutInit(&ac,av); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(512,512); glutCreateWindow(av[0]); glEnable(GL_DEPTH_TEST); if (shadepolys) { glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); } glMatrixMode(GL_PROJECTION); gluPerspective(45.0, 1.0/1.0, 0.1, 10.0); glMatrixMode(GL_MODELVIEW); glTranslatef(0.0, 0.0, -3.0); thesurf = glGenLists(1); glNewList(thesurf,GL_COMPILE); if ((err = polygonize(blob, size, 20, 0.,0.,0., triangle)) != NULL) { fprintf(stderr, "%s\n", err); exit(1); } printf ("%d vertices, %d triangles\n", nvertices, ntriangles); glEndList(); glutDisplayFunc(display); glutIdleFunc(idle); glutMainLoop(); } #else /***********************************************************************/ int gntris; /* global needed by application */ VERTICES gvertices; /* global needed by application */ /* triangle: called by polygonize() for ea. triangle; write to stdout */ int triangle (int i1, int i2, int i3, VERTICES vertices) { gvertices = vertices; gntris++; fprintf(stdout, "%d %d %d\n", i1, i2, i3); return 1; } /* main: call polygonize() with blob function * write points-polygon formatted data to stdout */ main () { int i; char *err; gntris = 0; fprintf(stdout, "triangles\n\n"); if ((err = polygonize(blob, .1, 20, 0.,0.,0., triangle)) != NULL) { fprintf(stdout, "%s\n", err); exit(1); } fprintf (stdout, "\n%d triangles, %d vertices\n", gntris, gvertices.count); fprintf(stdout, "\nvertices\n\n"); for (i = 0; i < gvertices.count; i++) { VERTEX v; v = gvertices.ptr[i]; fprintf(stdout, "%f %f %f\t%f %f %f\n", v.position.x, v.position.y, v.position.z, v.normal.x, v.normal.y, v.normal.z); } fprintf(stderr, "%d triangles, %d vertices\n", gntris, gvertices.count); exit(0); } #endif /*********** endif for SGIGFX *****************************************/ #endif /*********** endif for NOMAIN *****************************************/ /**** An Implicit Surface Polygonizer ****/ /* polygonize: polygonize the implicit surface function * arguments are: * float function (float x, float y, float z) * the implicit surface function given an arbitrary point * return negative for inside, positive for outside * float size * width of the partitioning cube * int bounds * max. range of cubes (+/- on the three axes) from first cube * float x, y, z * coordinates of a starting point on or near the surface * may be defaulted to 0., 0., 0. * int triproc (i1, i2, i3, vertices) * int i1, i2, i3 (indices into the vertex array) * VERTICES vertices (the vertex array, indexed from 0) * called for each triangle * the triangle coordinates are (for i = i1, i2, i3): * vertices.ptr[i].position.x, .y, and .z * vertices are ccw when viewed from the out (positive) side * in a left-handed coordinate system * vertex normals point outwards * return 1 to continue, 0 to abort * returns error or NULL */ char *polygonize ( float (*function)(float x, float y, float z), float size, int bounds, float x, float y, float z, int (*triproc)(int i1, int i2, int i3, VERTICES vertices)) { int n; PROCESS p; TEST in, out; p.function = function; p.triproc = triproc; p.size = size; p.bounds = bounds; p.delta = size/(float)(RES*RES); /* allocate hash tables: */ p.centers = (CENTERLIST **) mycalloc(HSIZE, sizeof(CENTERLIST *)); p.corners = (CORNERLIST **) mycalloc(HSIZE, sizeof(CORNERLIST *)); p.edges = (EDGELIST **) mycalloc(2*HSIZE, sizeof(EDGELIST *)); p.vertices.count = p.vertices.max = 0; /* no vertices yet */ p.vertices.ptr = NULL; /* find point on surface, beginning search at (x, y, z): */ srand(1); in = find(1, &p, x, y, z); out = find(0, &p, x, y, z); if (!in.ok || !out.ok) { freeprocess(&p); if (!in.ok) printf ("in not ok\n"); if (!out.ok) printf ("out not ok\n"); return "can't find starting point"; } converge(&in.p, &out.p, in.value, p.function, &p.start); /* push initial cube on stack: */ p.cubes = (CUBES *) mycalloc(1, sizeof(CUBES)); /* list of 1 */ p.cubes->cube.i = p.cubes->cube.j = p.cubes->cube.k = 0; p.cubes->next = NULL; /* set corners of initial cube: */ for (n = 0; n < 8; n++) p.cubes->cube.corners[n] = \ setcorner(&p, BIT(n,2), BIT(n,1), BIT(n,0)); setcenter(p.centers, 0, 0, 0); while (p.cubes != NULL) { /* process active cubes till none left */ CUBE c; CUBES *temp = p.cubes; c = p.cubes->cube; /* decompose into tetrahedra and polygonize: */ if (!(dotet(&c, LBN, LTN, RBN, LBF, &p) && dotet(&c, RTN, LTN, LBF, RBN, &p) && dotet(&c, RTN, LTN, LTF, LBF, &p) && dotet(&c, RTN, RBN, LBF, RBF, &p) && dotet(&c, RTN, LBF, LTF, RBF, &p) && dotet(&c, RTN, LTF, RTF, RBF, &p))) { freeprocess(&p); return "aborted"; } /* pop current cube from stack */ p.cubes = p.cubes->next; free((char *) temp); /* test six face directions, maybe add to stack: */ testface(c.i-1, c.j, c.k, &c, L, LBN, LBF, LTN, LTF, &p); testface(c.i+1, c.j, c.k, &c, R, RBN, RBF, RTN, RTF, &p); testface(c.i, c.j-1, c.k, &c, B, LBN, LBF, RBN, RBF, &p); testface(c.i, c.j+1, c.k, &c, T, LTN, LTF, RTN, RTF, &p); testface(c.i, c.j, c.k-1, &c, jbN, LBN, LTN, RBN, RTN, &p); testface(c.i, c.j, c.k+1, &c, F, LBF, LTF, RBF, RTF, &p); } freeprocess(&p); return NULL; } /* freeprocess: free all allocated memory */ int freeprocess (PROCESS *p) { int index; CORNERLIST *l, *lnext; CENTERLIST *cl, *clnext; EDGELIST *edge, *edgenext; for (index = 0; index < HSIZE; index++) for (l = p->corners[index]; l; l = lnext) { lnext = l->next; free((char *) l); /* free CORNERLIST */ } for (index = 0; index < HSIZE; index++) for (cl = p->centers[index]; cl; cl = clnext) { clnext = cl->next; free((char *) cl); /* free CENTERLIST */ } for (index = 0; index < 2*HSIZE; index++) for (edge = p->edges[index]; edge; edge = edgenext) { edgenext = edge->next; free((char *) edge); /* free EDGELIST */ } free((char *) p->edges); /* free array EDGELIST ptrs */ free((char *) p->corners); /* free array CORNERLIST ptrs */ free((char *) p->centers); /* free array CENTERLIST ptrs */ if (p->vertices.ptr) free((char *) p->vertices.ptr); /* free VERTEX array */ } /* testface: given cube at lattice (i, j, k), and four corners of face, * if surface crosses face, compute other four corners of adjacent cube * and add new cube to cube stack */ testface ( int i, int j, int k, CUBE *old, int face, int c1, int c2, int c3, int c4, PROCESS *p) { CUBE new; CUBES *oldcubes = p->cubes; static int facebit[6] = {2, 2, 1, 1, 0, 0}; int n, pos = old->corners[c1]->value > 0.0 ? 1 : 0; int bit = facebit[face]; /* test if no surface crossing, cube out of bounds, or prev. visited? */ if ((old->corners[c2]->value > 0) == pos && (old->corners[c3]->value > 0) == pos && (old->corners[c4]->value > 0) == pos) return; if (abs(i) > p->bounds || abs(j) > p->bounds || abs(k) > p->bounds) return; if (setcenter(p->centers, i, j, k)) return; /* create new cube: */ new.i = i; new.j = j; new.k = k; for (n = 0; n < 8; n++) new.corners[n] = NULL; new.corners[FLIP(c1, bit)] = old->corners[c1]; new.corners[FLIP(c2, bit)] = old->corners[c2]; new.corners[FLIP(c3, bit)] = old->corners[c3]; new.corners[FLIP(c4, bit)] = old->corners[c4]; for (n = 0; n < 8; n++) if (new.corners[n] == NULL) new.corners[n] = setcorner(p, i+BIT(n,2), j+BIT(n,1), k+BIT(n,0)); /*add cube to top of stack: */ p->cubes = (CUBES *) mycalloc(1, sizeof(CUBES)); p->cubes->cube = new; p->cubes->next = oldcubes; } /* setpoint: set point location given lattice location */ setpoint (jbPOINT *pt, int i, int j, int k, PROCESS *p) { pt->x = p->start.x+((float)i-0.5)*p->size; pt->y = p->start.y+((float)j-0.5)*p->size; pt->z = p->start.z+((float)k-0.5)*p->size; } /* setcorner: return corner with the given lattice location set (and cache) its function value */ CORNERLIST *setcorner (PROCESS *p, int i, int j, int k) { /* for speed, do corner value caching here */ int index = HASH(i, j, k); CORNERLIST *l = p->corners[index]; jbPOINT pt; for (; l != NULL; l = l->next) if (l->i == i && l->j == j && l->k == k) return l; setpoint (&pt, i, j, k, p); l = (CORNERLIST *) mycalloc(1, sizeof(CORNERLIST)); l->i = i; l->j = j; l->k = k; l->value = p->function(pt.x, pt.y, pt.z); l->next = p->corners[index]; p->corners[index] = l; return l; } /* find: search for point with value of given sign (0: neg, 1: pos) */ TEST find (int sign, PROCESS *p, float x, float y, float z) { int i; TEST test; float range = p->size; test.ok = 1; for (i = 0; i < 10000; i++) { test.p.x = x+range*(RAND()-0.5); test.p.y = y+range*(RAND()-0.5); test.p.z = z+range*(RAND()-0.5); test.value = p->function(test.p.x, test.p.y, test.p.z); if (sign == (test.value > 0.0)) return test; range = range*1.0005; /* slowly expand search outwards */ } test.ok = 0; return test; } /**** Tetrahedral Polygonization ****/ /* dotet: triangulate the tetrahedron * b, c, d should appear clockwise when viewed from a * return 0 if client aborts, 1 otherwise */ int dotet (CUBE *cube, int c1, int c2, int c3, int c4, PROCESS *p) { CORNERLIST *a = cube->corners[c1]; CORNERLIST *b = cube->corners[c2]; CORNERLIST *c = cube->corners[c3]; CORNERLIST *d = cube->corners[c4]; int index = 0, apos, bpos, cpos, dpos, e1, e2, e3, e4, e5, e6; if (apos = (a->value > 0.0)) index += 8; if (bpos = (b->value > 0.0)) index += 4; if (cpos = (c->value > 0.0)) index += 2; if (dpos = (d->value > 0.0)) index += 1; /* index now 4-bit number equal to one of the 16 possible cases */ if (apos != bpos) e1 = vertid(a, b, p); if (apos != cpos) e2 = vertid(a, c, p); if (apos != dpos) e3 = vertid(a, d, p); if (bpos != cpos) e4 = vertid(b, c, p); if (bpos != dpos) e5 = vertid(b, d, p); if (cpos != dpos) e6 = vertid(c, d, p); /* 14 productive tet. cases (0000 and 1111 do not yield polygons */ switch (index) { case 1: return p->triproc(e5, e6, e3, p->vertices); case 2: return p->triproc(e2, e6, e4, p->vertices); case 3: return p->triproc(e3, e5, e4, p->vertices) && p->triproc(e3, e4, e2, p->vertices); case 4: return p->triproc(e1, e4, e5, p->vertices); case 5: return p->triproc(e3, e1, e4, p->vertices) && p->triproc(e3, e4, e6, p->vertices); case 6: return p->triproc(e1, e2, e6, p->vertices) && p->triproc(e1, e6, e5, p->vertices); case 7: return p->triproc(e1, e2, e3, p->vertices); case 8: return p->triproc(e1, e3, e2, p->vertices); case 9: return p->triproc(e1, e5, e6, p->vertices) && p->triproc(e1, e6, e2, p->vertices); case 10: return p->triproc(e1, e3, e6, p->vertices) && p->triproc(e1, e6, e4, p->vertices); case 11: return p->triproc(e1, e5, e4, p->vertices); case 12: return p->triproc(e3, e2, e4, p->vertices) && p->triproc(e3, e4, e5, p->vertices); case 13: return p->triproc(e6, e2, e4, p->vertices); case 14: return p->triproc(e5, e3, e6, p->vertices); } return 1; } /**** Storage ****/ /* mycalloc: return successful calloc or exit program */ char *mycalloc (int nitems, int nbytes) { char *ptr = (char *) calloc(nitems, nbytes); if (ptr != NULL) return ptr; fprintf(stderr, "can't calloc %d bytes\n", nitems*nbytes); exit(1); } /* setcenter: set (i,j,k) entry of table[] * return 1 if already set; otherwise, set and return 0 */ int setcenter(CENTERLIST *table[], int i, int j, int k) { int index = HASH(i, j, k); CENTERLIST *new, *l, *q = table[index]; for (l = q; l != NULL; l = l->next) if (l->i == i && l->j == j && l->k == k) return 1; new = (CENTERLIST *) mycalloc(1, sizeof(CENTERLIST)); new->i = i; new->j = j; new->k = k; new->next = q; table[index] = new; return 0; } /* setedge: set vertex id for edge */ setedge ( EDGELIST *table[], int i1, int j1, int k1, int i2, int j2, int k2, int vid) { unsigned int index; EDGELIST *new; if (i1>i2 || (i1==i2 && (j1>j2 || (j1==j2 && k1>k2)))) { int t=i1; i1=i2; i2=t; t=j1; j1=j2; j2=t; t=k1; k1=k2; k2=t; } index = HASH(i1, j1, k1) + HASH(i2, j2, k2); new = (EDGELIST *) mycalloc(1, sizeof(EDGELIST)); new->i1 = i1; new->j1 = j1; new->k1 = k1; new->i2 = i2; new->j2 = j2; new->k2 = k2; new->vid = vid; new->next = table[index]; table[index] = new; } /* getedge: return vertex id for edge; return -1 if not set */ int getedge (EDGELIST *table[], int i1, int j1, int k1, int i2, int j2, int k2) { EDGELIST *q; if (i1>i2 || (i1==i2 && (j1>j2 || (j1==j2 && k1>k2)))) { int t=i1; i1=i2; i2=t; t=j1; j1=j2; j2=t; t=k1; k1=k2; k2=t; } q = table[HASH(i1, j1, k1)+HASH(i2, j2, k2)]; for (; q != NULL; q = q->next) if (q->i1 == i1 && q->j1 == j1 && q->k1 == k1 && q->i2 == i2 && q->j2 == j2 && q->k2 == k2) return q->vid; return -1; } /**** Vertices ****/ /* vertid: return index for vertex on edge: * c1->value and c2->value are presumed of different sign * return saved index if any; else compute vertex and save */ int vertid (CORNERLIST *c1, CORNERLIST *c2, PROCESS *p) { VERTEX v; jbPOINT a, b; int vid = getedge(p->edges, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k); if (vid != -1) return vid; /* previously computed */ setpoint (&a, c1->i, c1->j, c1->k, p); setpoint (&b, c2->i, c2->j, c2->k, p); converge (&a, &b, c1->value, p->function, &v.position); /* posn. */ vnormal(&v.position, p, &v.normal); /* normal */ vid = addtovertices(&p->vertices, v); /* save */ setedge(p->edges, c1->i, c1->j, c1->k, c2->i, c2->j, c2->k, vid); return vid; } /* addtovertices: add v to sequence of vertices and return its id */ int addtovertices (VERTICES *vertices, VERTEX v) { if (vertices->count == vertices->max) { int i; VERTEX *new; vertices->max = vertices->count == 0 ? 10 : 2*vertices->count; new = (VERTEX *) mycalloc((unsigned)vertices->max,sizeof(VERTEX)); for (i = 0; i < vertices->count; i++) new[i] = vertices->ptr[i]; if (vertices->ptr != NULL) free((char *) vertices->ptr); vertices->ptr = new; } vertices->ptr[vertices->count++] = v; return (vertices->count-1); } /* vnormal: compute unit length surface normal at point */ vnormal (jbPOINT *point, PROCESS *p, jbPOINT *v) { float f = p->function(point->x, point->y, point->z); v->x = p->function(point->x+p->delta, point->y, point->z)-f; v->y = p->function(point->x, point->y+p->delta, point->z)-f; v->z = p->function(point->x, point->y, point->z+p->delta)-f; f = sqrt(v->x*v->x + v->y*v->y + v->z*v->z); if (f != 0.0) {v->x /= f; v->y /= f; v->z /= f;} } /* converge: from two points of differing sign, converge to surface */ converge ( jbPOINT *p1, jbPOINT *p2, float v, float (*function)(float x, float y, float z), jbPOINT *p) { int i = 0; jbPOINT pos, neg; if (v < 0) { pos.x = p2->x; pos.y = p2->y; pos.z = p2->z; neg.x = p1->x; neg.y = p1->y; neg.z = p1->z; } else { pos.x = p1->x; pos.y = p1->y; pos.z = p1->z; neg.x = p2->x; neg.y = p2->y; neg.z = p2->z; } for (;;) { p->x = 0.5*(pos.x + neg.x); p->y = 0.5*(pos.y + neg.y); p->z = 0.5*(pos.z + neg.z); if (i++ == RES) return; if ((function(p->x, p->y, p->z)) > 0.0) {pos.x = p->x; pos.y = p->y; pos.z = p->z;} else {neg.x = p->x; neg.y = p->y; neg.z = p->z;} } }